Dagger 2 — fully static, compile-time dependency injection framework is a backbone of code architecture in Azimo Android app. We already know that with growing dev team clean code structure is one of the most important things in every project. Initialisation/usage separation, easier testing (unit or functional), better scalability —those are just a few of the benefits that come from use of dependency injection framework like Dagger 2.
Nowadays we can find a lot of tutorials and code examples showing how to start with Dagger 2 and dependency injection in Android. If this is what you are looking for, here is my posts series which can be helpful:
- Introduction to Dependency Injection
- Dagger 2 API
- Dagger 2 — custom scopes
- Dagger 2 — graph creation performance
- Dependency injection with Dagger 2 — Producers
- Async Injection in Dagger 2 with RxJava
- Inject everything — ViewHolder and Dagger 2 (with Multibinding and AutoFactory example)
But those just show how to start working with DI. That’s why today we would like to start sharing our experiences with Dagger 2 after almost two years of having this in our production app.
@Component vs @Subcomponent
It’s not always clear in simple tutorials but custom scopes are one of the most powerful features in Dagger 2. In more complex apps using just @Singleton scope won’t make your live easier.
Let’s consider simple example — we have dependencies which are strictly connected with currently logged in user (e.g. UserProfilePresenter class which is responsible for logic in user profile screen). Instead of making call to our data store every time when we need user entity we can create @User scope and keep all related dependencies as a single instances in UserComponent which lives as long as user is logged in.
Using custom scopes in production app isn’t subject of this post but we’ll also cover this in close future.
In Dagger 2 we have two ways of building custom scopes and components that inherit and extend the object graph:
- We can build another @Component which explicitly shows which Component is extended (AppComponent in this case)
- Or we can define @Subcomponent which is created from base component with abstract factory method (read more):
What is the difference between those two approaches?
Documentation tells about difference in dependencies visibility. @Subcomponent has access to all dependencies from his parent while dependent @Component has access only to those dependencies which are exposed publicly in base @Component interface.
Having this in mind we initially picked approach with dependent @Component. All components in our project depended on AppComponent scoped with @Singleton.
When methods count matters…
But there is another important difference between @Component and @Subcomponent. Let’s take a look at generated code. Subcomponent implementation is just an inner class in base component.
So for UserProfileActivityComponent which depends on AppComponent generated code would look like this:
Lines 20–31 show us how Dagger provides needed dependencies from AppComponent to UserProfileActivityComponent. Subcomponent has access to base component provider fields so they can be used directly.
Different situation is in dependent @Component. The same structure (UserProfileActivityComponent depends on AppComponent) will result which this generated code:
Lines 14–34 show that each dependency requested from AppComponent needs to have his own method which produces Provider<> object for it. Why? Because dependent component has access only to those dependencies in base component which are exposed with its public interface.
What does it mean for us — Android developers? That each dependency in dependent component which is taken from base component brings us closer to 65k methods limit:
And because of this we decided to migrate our all dependent @Components to @Subcomponent. In result of this operation we reduced our code base by ~5000 additional methods.
Analyze your APK quickly
At the end it’s worth mentioning that starting from Android Studio 2.2 there is a new feature which gives us way to analyze APK file. It can be accessed from Build -> Analyze APK….
This tool allows us to check size of particular components (classes, resources etc.) and what was the most helpful for us — to get basic informations about .dex files (classes/methods count). And it was our starting point in dependency injection optimizations.
That’s all for today, but stay in touch. Soon we’ll share more our experiences with Dagger 2 and dependency injection in our Android app. Thanks for reading!