Dagger for Android: A Beginner’s Guide — Part 2

Avishek Khan
Monstar Lab Bangladesh Engineering
9 min readSep 7, 2019

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:

App-scoped component contains objects included in its installed modules. Activity-scoped subcomponent contains those included in its installed modules and all of the app component. A Fragment-scoped subcomponent contains those installed into it and everything that its Activity-scoped parent has. Note that the size of the scope increases as we go inward in the diagram— the inner the scope, the larger its lifespan.

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.

MainActivity::mainViewModel is set by Dagger after the AndroidInjection.inject(this) call
CatFragment::mainViewModel is assigned the same object by Dagger after AndroidSupportInjection.inject(this) is called
This is what you get after evaluating mainViewModel in the activity and its fragments

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 having AppComponent’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 install NetworkModule or CatModule into the MainActivity 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 in CatModule can depend on Retrofit in NetworkModule because both objects have the same scope and both modules are installed into AppComponent.

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!

--

--

Avishek Khan
Monstar Lab Bangladesh Engineering

Software Engineer. I love Java as a coffee drink and Kotlin as a programming language. Passionate about science, technology, fitness, traveling, and tequila.