Dagger for Android: A Beginner’s Guide — Part 2
This is part of a series based on my project that we are going to improve step by step and learn various Dagger concepts as we advance. We’re going to pick up where we left off in part 1 and introduce here the scoped bindings. If you haven’t read the previous post, I would strongly recommend you do so.
If you want to skip part 1, you can still clone the repository and checkout part-2
branch which contains everything we did previously.
Before we begin, allow me to recapitulate what we’ve done in part 1.
We created AppComponent
having two subcomponents bound to MainActivity
and CatFragment
. Both MainActivity
and CatFragment
depend on MainViewModel
which in turn depends on CatRepository
and so on. We have set up Dagger so that all the dependencies are satisfied when the activity or fragment is injected with the MainViewModel
instance. But, there is a caveat — every time the activity or fragment is injected, Dagger instantiates a new instance of MainViewModel
. But, we want it to be instantiated once — only when the activity is created by the system and CatFragment
would reuse that object regardless of how many times a new instance of the fragment is created upon clicking the items on the list displayed in MainActivity
. The sharing of view model between activity and fragment is a pretty neat approach and that’s how you’re recommended to share data or communicate among multiple fragments in an activity, though the proper implementation would be a bit different from ours which we will come to in part 3.
Anyway, the multiple instantiations of view model can be resolved by using scopes. But before delving into details, let’s realise a few things first.
Every component or subcomponent defined in our Dagger setup represents an Android component i.e.- Application
, Activity
, Fragment
etc. The AppComponent
represents our Application
class, PurrfectApp
, and its two subcomponents represent our MainActivity
and CatFragment
. Like Android components, a Dagger component (or subcomponent) has its own lifecycle which is the same as the Android component it’s bound to. Let me explain.
After compilation is done, Dagger generates a concrete implementation of the component or subcomponent that we’ve defined during our Dagger configuration. AppComponent
is internally implemented by DaggerAppComponent
which is instantiated in PurrfectApp
’s onCreate()
function when we call DaggerAppComponent.create().inject(this)
. Since an Application
class is instantiated only once when the app is launched and lives throughout the entire lifetime of the app, this AppComponent
implementation has the same lifespan as our app.
Similarly, the subcomponent implementation bound to MainActivity
has the same lifecycle as MainActivity
and is internally instantiated by Dagger when we call AndroidInjection.inject(this)
. The same goes for CatFragment
when we call AndroidSupportInjection.inject(this)
in its onAttach()
.
These component or subcomponent implementations contain the objects that Dagger instantiates to create the dependency graph. Therefore, the lifespans of those objects are the same as the component they reside in.
As we all know, there is a hierarchical relationship among the Android components in terms of the lifecycle. Application
has the longest lifespan followed by Activity
which, again, is followed by Fragment
. Within one lifetime of an Application
, there can be many activities or multiple instances of an Activity
due to multiple starts from other activities or when an activity is recreated by the system, e.g., during a configuration change. Similarly, multiple instances of a Fragment
can be created within a single lifetime of an Activity
— for example, new CatFragment
instances are created in our MainActivity
when items on our list of cat images are clicked. To put it simply, every Android component has a scope, and some scopes are larger than others. This hierarchy of scopes can be shown as follows:
Just like their Android counterparts, Dagger components can be associated with a scope. If done properly, they will correspond to a similar hierarchy.
In Dagger, an object can be associated with a scope too, so that it will be instantiated inside the corresponding scoped component only once and reused inside the component as well as its descendant components (i.e., subcomponents).
An object instantiated in an ancestor scope can be reused inside multiple instances of a descendent scope. This is important, because, as we will see, a scoped object associated with a component can depend on an object whose scope is associated with one of the ancestor components, but not vice-versa.
Currently, in our project, we haven’t told Dagger which component is associated with which scope. Since Dagger doesn’t know which scope the objects belong to, every time Dagger injects an Android component such as MainActivity
or CatFragment
, it instantiates a new object. But if we need to tell Dagger that only one instance of MainViewModel
has to be used as long as the subcomponent bound to our MainActivity
is alive, we have to associate the subcomponent binding with a scope and tell Dagger that our MainViewModel
is associated with that scope. Let’s see how.
Create activity scope
Create an annotation
class annotated with @Scope
. In our project, I’ve already created a scope named ActivityScope
which is located in the scope
package.
Now, annotate the MainActivity
subcomponent binding with this scope:
Now that Dagger knows which scope MainActivity
subcomponent represents, we can tell Dagger that MainViewModel
is supposed to be instantiated inside our MainActivity
subcomponent by annotating it with @ActivityScope
:
This is an implicit binding which tells Dagger that it can be used by any component associated with the @ActivityScope
or any of its subcomponents. In our case, the component is bound to MainActivity
and has the same lifespan as the activity. Since MainViewModel
is now scoped, its object, too, will live just as long as the activity.
But MainViewModel
is also a dependency of CatFragment
. Therefore, the subcomponent binding of CatFragment
needs to be associated with this scope as well, otherwise, CatFragment
cannot be injected with this object. This is why if you build the project now, the compilation will fail. I would recommend trying it out anyway and seeing the error message to get familiar with it. At least you’ll know what may go wrong just in case you encounter something similar in the future. 😉
To fix this, we can’t just use@ActivityScope
for CatFragment
subcomponent binding. Because if we do, it will cause the very problem we’re trying to fix — every time a new CatFragment
instance will be created, a new MainViewModel
will be instantiated inside a new CatFragment
subcomponent instance since both will be sharing the same scope (remember, there will be a new instance of subcomponent for each fragment). As said earlier, since a descendant component can reuse objects from any of its ancestor components, CatFragment
subcomponent needs to be nested inside the MainActivity
component. In other words, it needs to be a child component of the MainActivity
component so that it matches the hierarchy we discussed above.
Create nested subcomponent
Remove MainFragmentBindingModule
from AppComponent
and install it into the MainActivity
subcomponent:
Since MainActivity
has a subcomponent now, it has to implement HasAndroidInjector
, similar to our Application
class:
Now run the app and voila! If you debug the app, you can see that the same MainViewModel
object that Dagger is setting to our mainViewModel
field of MainActivity
, is reused in each new CatFragment
instance.
Now, what if we rotate the screen? The Android system will recreate the activity resulting in a new subcomponent instance along with a new MainViewModel
object. Consider another scenario. Suppose, our project gets bigger. There will be another activity which, for example, will display a list of favorite cats. Our ActivityBindingModule
will have another subcomponent binding for that activity with @ActivityScope
. The view model of this activity also might depend on CatRepository
which might be extended to have another function to fetch a list of favorite cats. In both scenarios, CatDataRepository
will be instantiated each time the view model object is created since there is no implicit binding of CatDataRepository
as to which component it should belong to. Since there is no reason to create a new instance of CatRepository
for every activity, which is expensive in terms of resource allocation, we can associate it with the parent scope of all the activity components, so that every activity can share the same CatRepository
instance. Simply put, we need a scope for our AppComponent
and CatDataRepository
needs to be associated with it.
Creating application scope
To create a scope representing our application itself, we can create another @Scope
annotation
class just like we did for @ActivityScope
. But, there’s already a scope named @Singleton
that comes preloaded with the javax
package. We can simply use it on our AppComponent
:
Now tell Dagger that CatDataRepository
should be associated with AppComponent
by using the same scope for it:
What if we have more repositories that depend on CatService
? Or more services that depend on Retrofit
? Instead of creating a new CatService
for each repository or a new Retrofit
instance for each service, we can bind their @Provides
functions to @Singleton
as well so that only one instance of them will be reused throughout the entire lifetime of our application.
Since Retrofit
will now be instantiated only once in the entire lifetime of our app, all the objects that Retrofit
depends upon will be instantiated once as well. So we can just leave them as they are. But remember, if we need to scope a @Provides
function inside any of the modules, we need to use the same scope as the component it is installed into, i.e., @Singleton
for AppComponent
or @ActivityScope
for MainActivity
(or any other activity) subcomponent's module functions.
That’s pretty much it. But before finishing off, let me jot down a few important notes:
- While sibling subcomponents can be associated with the same scope, no subcomponents can be associated with the same scope as any of its ancestors. For example, we can add another activity subcomponent in the
ActivityBindingModule
having@ActivityScope
, but we cannot create any subcomponent havingAppComponent
’s@Singleton
scope. - A module containing a scope-annotated
@Provides
function needs to be installed into the component/subcomponent annotated with the same scope. For instance, we cannot installNetworkModule
orCatModule
into theMainActivity
subcomponent because they have@Singleton
-scoped@Provides
functions. - A scoped object in a module can depend on another object with the same scope even if the latter is defined in another module given that both modules are installed in the same component/subcomponent associated with the scope. For example,
CatService
inCatModule
can depend onRetrofit
inNetworkModule
because both objects have the same scope and both modules are installed intoAppComponent
.
These are some of the basic rules of scoped binding. As long as you keep them in mind, you shouldn’t run into any problem.
There’s one more issue in our project though — Dagger is creating a new ViewModel
instance even if the activity is recreated due to configuration changes such as screen rotation. But ViewModel
is a lifecycle-aware component that can survive configuration changes. That means, only one instance of a ViewModel
should be created for an activity no matter how many times the activity gets recreated. This can’t be done using a @Singleton
scope for MainViewModel
either since the object needs to be instantiated after the activity is created for the first time. Dagger provides us with a neat solution to this problem which is discussed in great detail in the next part. You can check that out here:
That’s all for now. Please do share your thoughts. You can reach out to me on Twitter: @Avishek_Khan. You might also want to check out more tech posts on our blog. Until next time, happy learning!