Scoping Activities Subcomponents with dagger-android (1/2)
We have started to use Dagger2 in our existing Android application about one and half years ago.
Minimum Viable Injection
We opted for a simple structure with one AppComponent and one Subcomponent (generated by dagger-android) for each one of our activities. There are also subcomponents for each one of our fragments (also generated by dagger-android).
The structure of the components looks like this:
This was at first hard to grasp and we have spent some time researching the best way to setup dagger-android in an existing Android application. I personally think that the most confusing part was that there were different ways of achieving the same result using dagger2 in Android. Therefore, for someone researching this on the Internet, it was hard to get the big picture.
Today, a user may find everything he needs to know in order to start using Dagger2 in Android just here in the official documentation of Dagger.
Without duplicating most the information available in the documentation linked above, I will show the most important lines of code written in order to have the structure illustrated above. I will purposely start with the lower most level: the fragment and gradually go up to the parent.
Line 39 -> The @Module annotation tells dagger that this class contains directives on how to obtain some dependencies of the object graph.
Line 40 -> The module is made abstract (dagger latest recommendation) in order to let dagger take care of its implementation. Being abstract, makes it impossible for us to pass on references of dependencies to the module which could cause leaks since the lifecycle of the modules is out of our control.
abstract modules ensure that dagger takes care of all the code involved in the dependency injection of your application, not the developer.
Note that it is still possible to use have provide methods in the same file which defines your abstract using a companion object in kotlin
Line 42 -> The @FragmentScope annotation was created by us and is used to give a scope to all the dependencies provided the subcomponent dagger will generate for the fragment.
Scope are used to manage the lifetime of the objects in your object graph. No scope = a new object each time. A given scope = a single object for the whole scope. The most common scope is the singleton.
Line 43 -> The @ContributesAndroidInjector annotation (provided by dagger-android) triggers the generation of a subcomponent for all the dependencies this fragment contributes to. The modules which should be installed into the subcomponent of the fragment are listed in the modules parameters (i.e: EditorToolsFragment)
In dagger, installing a module into component means that component can contains all the objects provided / bound / held as private members (discouraged) by that module.
Line 44 -> Again an abstract methods which will be implemented by dagger during the code generation. Dagger-android just needs to know the type of the Fragment for which the subcomponent should be generated for.
Note that we have seen where we ask dagger to link a subcomponent to another in a child/parent relationship🕵🏾♂️ , we will see how that is done once we have presented the builder module of the activities.
Line 32 -> The @ActivityScope annotation was created by us to define a scope to link the lifetime of the objects provided by the modules to the that of their corresponding activity. We use it also to limit access to some objects in order to prevent leaking our activity related object. Let’s have a look at the class DebugActivityModule to see in more details how that scope is used.
In the module for the subcomponent of the DebugActivity, we include two another modules: SharedActivityModule and FragmentBuilderModule, where all dependencies provided by the activity are defined.
With dagger-android, we define a subcomponent as the child of another (sub)component by including its builder module to the list of the modules.
Thats the question to the question raised previously 🕵🏾♂️. In this case, line-> 14 adds all the subcomponents of each one of the fragments in FragmentBuilderModule, as child components of the DebugActivitySubcomponent generated by dagger-android.
The class SharedActivityBuilderModule is added to the modules of all of our activities because each one of our activities contributes in providing those dependencies (i.e: Context, ViewModelProviders, etc…).
The app component is the root-most component of our dagger object graph.
Line 14 -> We tell dagger that all the subcomponent generated by the ActivityBuilderModule should be created as child subcomponents of the app component. This allows dagger-android to inject our activities with their dependencies provided by the AppModule.
That completes the definition of our initial component hierarchy:
Fragment <- Activities <- App.
The end of the status quo
Recently, our team was confronted to a problem which forced us to reconsider the simplistic structure we have decided to adopt with our dagger components.
After spending many hours understanding the problem we realized that the lifetime of one of our objects, needed to be longer than the one of one or more of our activities while it lifetime has to be shorter than the lifetime of the application.
Problem: (1..N) * Lifetime Activities < Lifetime Object < Lifetime Application
Immediately, we understood that dagger could help us achieve such behaviour, but it was only after few hours that we understood that a more accurate statement of the problem was:
Problem: Lifetime Object = User Session
That seemed like a simpler problem to solve and again , we knew (thought) that dagger could help us solve that fairly easily. Here is the new structure of components that we needed:
Some of our activities components needed to become subcomponent of the new User subcomponent which itself would be a subcomponent of the app component.
The can-of-worm was discovered when we realized that dagger-android does not allow that based on their official documentation:
The highlighted text above basically says that only the app component can be the parent of the activities subcomponent. Euh…, come again!
There goes more hours trying to confront the cold and hard truth and it came down to basically picking between the following options:
Drop dagger-android and manage the injection of our android objects ourselves. It involves writing a lot of boiler plate code for the creating a subcomponent for each activity, fragment, etc…
Manage ourselves the lifecycle of the object which needed a unusual lifetime and creating static states 🤮. This basically had two sub-options:
1 — Binds the lifetime of that object to the one of the app and update it when the state of the app changes
2 — Binds the lifetime of that object to the lifetime of the activities and propagate its states to the next activity
We almost opted for the option B — 2 but then we realized that another object in our app needed that special lifetime. Based on Martin Fowler Rule’s of Three , this issue deserved a refactoring because it would definitely raise again in the near future.
In my next article, I will share how we were able to reach the component hierarchy we wanted while keeping dagger-android.