Web Analytics Made
Easy - StatCounter

Menu

About us Contact us

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

Tech 2019 / 09 / 09

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.

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

If you want to skip part 1, you can still clone the repository and checkout part-2branch which contains everything we did previously.

AVI5HEK/Purrfect-App

・・・

 

Before we begin, allow me to recapitulate what we’ve done in part 1.

 

We created AppComponenthaving two subcomponents bound to MainActivityand CatFragment. Both MainActivityand CatFragmentdepend on MainViewModelwhich in turn depends on CatRepositoryand so on. We have set up Dagger so that all the dependencies are satisfied when the activity or fragment is injected with the MainViewModelinstance. 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 CatFragmentwould 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, Fragmentetc. The AppComponentrepresents our Applicationclass, PurrfectApp, and its two subcomponents represent our MainActivityand 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. AppComponentis internally implemented by DaggerAppComponentwhich is instantiated in PurrfectApp’s onCreate()function when we call DaggerAppComponent.create().inject(this). Since an Applicationclass is instantiated only once when the app is launched and lives throughout the entire lifetime of the app, this AppComponentimplementation has the same lifespan as our app.

 

Similarly, the subcomponent implementation bound to MainActivityhas the same lifecycle as MainActivityand is internally instantiated by Dagger when we call AndroidInjection.inject(this). The same goes for CatFragmentwhen 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. Applicationhas the longest lifespan followed by Activitywhich, again, is followed by Fragment. Within one lifetime of an Application, there can be many activities or multiple instances of an Activitydue 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 Fragmentcan be created within a single lifetime of an Activity— for example, new CatFragmentinstances are created in our MainActivitywhen 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 annotationclass annotated with @Scope. In our project, I’ve already created a scope named ActivityScopewhich is located in the scopepackage.

@Scope
@Retention(AnnotationRetention.RUNTIME)
annotation class ActivityScope

Now, annotate the MainActivitysubcomponent binding with this scope:

@Module
abstract class ActivityBindingModule {
  @ActivityScope // specify the scope here
  @ContributesAndroidInjector
  abstract fun contributeMainActivity(): MainActivity
}

Now that Dagger knows which scope MainActivitysubcomponent represents, we can tell Dagger that MainViewModelis supposed to be instantiated inside our MainActivitysubcomponent by annotating it with @ActivityScope:

@ActivityScope
class MainViewModel
@Inject
constructor(private val catRepository: CatRepository) : BaseViewModel()

 

This is an implicit binding which tells Dagger that it can be used by any component associated with the @ActivityScopeor any of its subcomponents. In our case, the component is bound to MainActivityand has the same lifespan as the activity. Since MainViewModelis now scoped, its object, too, will live just as long as the activity.

 

But MainViewModelis also a dependency of CatFragment. Therefore, the subcomponent binding of CatFragmentneeds to be associated with this scope as well, otherwise, CatFragmentcannot 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@ActivityScopefor CatFragmentsubcomponent binding. Because if we do, it will cause the very problem we’re trying to fix — every time a new CatFragmentinstance will be created, a new MainViewModelwill be instantiated inside a new CatFragmentsubcomponent 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, CatFragmentsubcomponent needs to be nested inside the MainActivitycomponent. In other words, it needs to be a child component of the MainActivitycomponent so that it matches the hierarchy we discussed above.

 

Create nested subcomponent

Remove MainFragmentBindingModulefrom AppComponentand install it into the MainActivitysubcomponent:

@Module
abstract class ActivityBindingModule {
  @ActivityScope
  @ContributesAndroidInjector(modules = [MainFragmentBindingModule::class]) // install MainFragmentBindingModule here
  abstract fun contributeMainActivity(): MainActivity
}

Since MainActivityhas a subcomponent now, it has to implement HasAndroidInjector, similar to our Applicationclass:

class MainActivity : AppCompatActivity(), HasAndroidInjector {
  // 1
  @Inject
  lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Any>
  // 2
  override fun androidInjector(): AndroidInjector<Any> {
    return dispatchingAndroidInjector
  }
  …
}

Now run the app and voila! If you debug the app, you can see that the same MainViewModelobject that Dagger is setting to our mainViewModelfield of MainActivity, is reused in each new CatFragmentinstance.

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 MainViewModelobject. Consider another scenario. Suppose, our project gets bigger. There will be another activity which, for example, will display a list of favorite cats. Our ActivityBindingModulewill have another subcomponent binding for that activity with @ActivityScope. The view model of this activity also might depend on CatRepositorywhich might be extended to have another function to fetch a list of favorite cats. In both scenarios, CatDataRepositorywill be instantiated each time the view model object is created since there is no implicit binding of CatDataRepositoryas to which component it should belong to. Since there is no reason to create a new instance of CatRepositoryfor 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 CatRepositoryinstance. Simply put, we need a scope for our AppComponentand CatDataRepositoryneeds to be associated with it.

 

Creating application scope

To create a scope representing our application itself, we can create another @Scopeannotationclass just like we did for @ActivityScope. But, there’s already a scope named @Singletonthat comes preloaded with Dagger. We can simply use it on our AppComponent:

@Singleton
@Component(
  …
)
interface AppComponent : AndroidInjector<PurrfectApp>

Now tell Dagger that CatDataRepositoryshould be associated with AppComponentby using the same scope for it:

@Singleton
class CatDataRepository
constructor(private val catService: CatService) : CatRepository

What if we have more repositories that depend on CatService? Or more services that depend on Retrofit? Instead of creating a new CatServicefor each repository or a new Retrofitinstance for each service, we can bind their @Providesfunctions to @Singletonas well so that only one instance of them will be reused throughout the entire lifetime of our application.

@Module
abstract class CatModule {
  …
  @Module
  companion object {
    @JvmStatic
    @Singleton
    @Provides
    fun provideCatService(retrofit: Retrofit): CatService {
      return retrofit.create(CatService::class.java)
    }
  }
}
@Module
class NetworkModule {
  …
  @Singleton
  @Provides
  fun provideRetrofit(httpClient: OkHttpClient, schedulerProvider: SchedulerProvider): Retrofit {
    return Retrofit.Builder().baseUrl("https://api.thecatapi.com/v1/")
      .addConverterFactory(GsonConverterFactory.create())
      .addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(schedulerProvider.io()))
      .client(httpClient)
      .build()
  }
}

Since Retrofitwill 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 @Providesfunction inside any of the modules, we need to use the same scope as the component it is installed into, i.e., @Singletonfor AppComponentor @ActivityScopefor 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 ActivityBindingModulehaving @ActivityScope, but we cannot create any subcomponent having AppComponent’s @Singletonscope.

A module containing a scope-annotated @Providesfunction needs to be installed into the component/subcomponent annotated with the same scope. For instance, we cannot install NetworkModuleor CatModuleinto the MainActivitysubcomponent because they have @Singleton-scoped @Providesfunctions.

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, CatServicein CatModulecan depend on Retrofitin NetworkModulebecause 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 ViewModelinstance even if the activity is recreated due to configuration changes such as screen rotation. But ViewModelis 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 @Singletonscope for MainViewModeleither 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 that we will discuss in great detail in the next part.

 

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!

Related Post :

Dagger for Android: A Beginners Guide — Part 1

You have ideas, We have solutions.

CONTACT US!