Sample Apps to Achieve 80% Shorter Build Times in Trendyol Android Project
Trendyol is a super-app. It has many large features combined in the same Android project. This large-size nature of a super-app project has its own challenges when it comes to building, testing and releasing.
In this article we’ll look at one of our initiatives to keep our build times low and productivity high: sample apps!
Let’s begin!
What are Sample Apps
Sample apps are application modules that build only a subset of the project. In an Android project you can have more than one application module. While the main app module builds the entire application, sample app modules build and run only a few features.
The idea behind sample apps is that people who work in one feature don’t change features or teams that often. They don’t need to compile the rest of the project. Compiling only the domain/feature they own is enough.
The main benefit of the sample apps is their short build times. Because the sample apps build only a smaller part of the project, their build time is much shorter than the full app’s build time. This helps us iterate faster while developing and testing.
The disadvantages of the sample apps are their maintenance costs and impact to Gradle configuration time. You need to keep them in a working condition, and if you create a lot of sample apps (modules), then Gradle’s configuration time will be longer.
Other than developing public features, you can use them to create internal demo applications. For example a sample app that demonstrates your company’s theme or common components. Another example can be a sample app to test a new library you are thinking of introducing.
If for any reason your company decides to release a feature as a separate app, this will be much easier for you to do. Because your feature will already be a separate app in your project. This scenario may sound like a stretch, but it’s not so crazy for a super-app.
How to Decide Which Features to Include
Selecting the features for your sample app is up to you and is different for each domain and app. You can consider your bounded context while selecting which features to choose.
In Trendyol, we have a sample app for every large feature, or also known as service channels. For example: Dolap, Meal, Groceries, International and of course Trendyol. The Run Configurations menu in Android Studio looks like this:
This is the sample app of the meal delivery feature:
The sample app directly shows the Meal feature. This sample app doesn’t have the other features (like Trendyol, Dolap etc.). Leaving the feature means leaving the app.
It’s better to keep the sample apps as small as possible. Application module’s code and the dependencies in Gradle build files should be as little as possible. The smaller the sample app is, the faster it will build and the easier it will be to maintain it.
Which features you want to include in your sample apps depends on your domain and your project. My recommendation is to select the features that are relevant to each other. This way the relevant features will be available in your sample app when you run it. This will keep the navigation between features at the minimum, so that you don’t have to show a fake screen often. We’ll talk about fake screen in a further chapter.
Statistics
Do the benefits outweigh the maintenance costs? Let’s look at the stats.
In the chart below, you can find the monthly average build time duration of the full app and sample apps.
Our full app build time is 240 seconds. The sample app with longest build time is 84 seconds. Around 65% shorter than the full app!
Meal and International apps take about 80% shorter time than the full app!
Considering the current size of our team, we build & run these apps around 7 thousand times a month.
Using the full app, builds would take around 466 hours, or ~58 workdays, or ~19 full days.
Using sample apps, builds would take around 116 hours, or ~15 workdays, or ~5 full days.
Monthly we save up around 43 days of engineering workdays, or 14 full days. These savings mean our productivity increased as much as having two new team members! These benefits will outweigh the costs in the long term.
Modularize and Decouple Your Project
If you want to introduce sample apps, you will need a modularized and decoupled project. If your feature requires another feature (or the entire project) to work, then you can’t create a sample app that is small enough to have the benefits.
Creating a sample app should be as simple as selecting the Gradle modules you want to include. Any extra configuration you have, will cause confusion and slow down your speed. Our goal should be to create a plug-and-play project where you only select the modules you want to use. Then while building the app, selected modules/features get attached/injected automatically.
It’s generally recommended to have a high cohesion and low coupling setup. This means the things that are relevant to each other would stay closer and things that are irrelevant would stay far apart. For example the Fragment, ViewModel, UseCases and Repository of a feature would be together in a module, just like package-by-feature.
In this chapter, I have a few recommendations for you to consider while decoupling your project.
Consider Applying Dependency Inversion and Using Dagger’s Multibindings
Imagine this scenario, you have a LogoutUseCase class and it depends on other library/feature modules and uses their implementation classes:
Why this can be a problem? Because LogoutUseCase is coupled with other use cases. You can’t use LogoutUseCase unless you provide Feature1 and Feature2.
How do we decouple this? One way is to apply dependency inversion, just like in the Ports and Adapters (Hexagonal) Architecture. We can define an interface called ClearOnLogoutUseCase and make LogoutUseCase depend on this new interface. After this change, the LogoutUseCase doesn’t know anything about any other feature implementations. It only knows the interface it has and controls: ClearOnLogoutUseCase.
Now the other modules/features can implement ClearOnLogoutUseCase and contribute to this Set<ClearOnLogoutUseCase>. If you use Dagger: it has this amazing feature called Multibindings that discovers the bindings (“provides” functions) and contribute them to a set or a map.
After these changes:
- We have decoupled LogoutUseCase from other features. The users of LogoutUseCase don’t need to know the other implementations/features/modules.
- Dagger discovers this ClearFeature1OnLogoutUseCase class and includes it to our Set<ClearOnLogoutUseCase>. If you have Feature1 in your sample app (in your classpath) then this ClearFeature1OnLogoutUseCase will be included in the LogoutUseCase, automatically (no other configuration). If you don’t have any feature that provides a ClearFeature1OnLogoutUseCase then they won’t be included. This is similar to a plug-in architecture.
This setup is useful because it decouples the modules and makes it a plug-and-play project. We only need to pick the Gradle modules to use. This set of Gradle modules work together without other configuration.
Consider Using Dagger Hilt or Square Anvil
With the vanilla Dagger and Dagger-Android, you need to reference your Dagger modules from Dagger components or other Dagger modules. You have to do this by hand which adds extra work.
Dagger Hilt and Square Anvil can both discover your Dagger modules and add them to Dagger components, automatically!
As a side benefit, this will make it easier for you to move your code and dependencies around, without worrying about the module references.
Consider Using AndroidX App Startup
If you are initializing things at app launch (such as libraries, your own tracking classes etc.) then consider using AndroidX App Startup library.
With the App Startup library, you create Initializer classes and the library discovers your Initializer classes and executes them at app startup, automatically!
It has a few features, but the main reason I’m suggesting, is because it can discover Initializers. You can have separate modules that contain different Initializers in your project. The apps depending on these modules won’t need to do any extra configuration. They won’t have to initialize them by hand. The Initializers will get discovered and executed automatically.
This aligns well with our plugin architecture goal, because this will reduce the required configuration in sample apps and the cognitive load in your team.
Consider Creating a Fake Screen
If your features are interacting with each other often, you might end up in a situation where the feature in sample app wants to navigate to another feature, but that other feature is not included in the build.
What should happen when the user of your sample app wants to navigate to a feature that’s not available? Should the app just crash? We thought, instead of crashing the app we can show a screen that says “the feature you are navigating is not available“. This screen can be Activity, Fragment or a Composable function. Optionally, we also display the navigation information (such as passed arguments or deeplinks) in this screen to make the development easier.
Depending on how you do navigation, you should be able to replace the actual feature with a dummy/fake feature in your sample apps. As an example, if you are using Fragments to navigate, you can create an interface. The actual implementation of the interface can go to actual feature. The dummy/fake implementation of the interface can go to “NavigatingOutOfBounds” screen.
At Trendyol, the features we include in our sample apps don’t interact with the features outside of the sample apps. We have this fake navigation screen, however it’s not widely used, because the feature set of sample apps are separated from the of the project. You can look at our previous article to get more idea on how are we doing this type of navigation.
TL;DR
Sample apps are one way to turn a large project into a small project. They will have benefits as well as maintenance costs. You need to decide whether they’re useful for your project. At Trendyol, we find them quite useful!
Keep it simple, keep it small.
More Info
If you like to learn more about sample apps and modularization:
- See how we modularize our project: Modularising Trendyol Android App for Build Efficiency
- Amazing article from Cash App Code Blog: Attacking Build Times With Sample Apps
Thanks Mert Nevzat Yüksel for proofreading this article and giving feedback!
Want to work in this team?
Do you want to join us on the journey of building the e-commerce platform that has the most positive impact?