Chapters

Hide chapters

Dagger by Tutorials

First Edition · Android 11 · Kotlin 1.4 · AS 4.1

16. Dagger & Android
Written by Massimo Carli

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

In the previous chapters of this book, you became a Dagger guru. You learned many new concepts, in particular:

  • How Dagger works and how it helps you implement the main principles of object-oriented programming in your app.
  • The different types of injection, how to know which one to use for a specific use case and how to implement them with Dagger.
  • How to use custom @Scopes to optimize the way your app uses resources.
  • When to use @Components or @Subcomponents to manage dependencies between objects with different @Scopes.
  • How to implement architecture based on the concept of plugins by using multibinding with Sets or Maps.
  • How to create a structure for your app’s code by splitting it into different modules, resulting in improved build times, reusability and extensibility.

You’ve done a great job. Dependency injection is a tough topic and Google is working to make it easier, improving the learning curve that, before this book, was very steep. The result of Google’s effort is Hilt.

You’ll learn about Hilt in the remaining chapters of this book. Before you get to that, though, take a moment to consider the legacy code you might need to maintain. To handle that, you’ll learn about Dagger Android in this chapter.

There’s still a lot of code out there that uses Dagger Android, which is a library that Google created with the goal of simplifying Dagger in Android apps. Unfortunately, the result was not very successful and the solution is sometimes more complicated than the problem it was supposed to solve.

For this reason, Google stopped developing new features for the Dagger Android library in favor of Hilt. As you’ll learn in this chapter, Dagger Android helps reduce the lines of code you need to write to configure Dagger, but it also has some important limitations. You’ll get to know about these limitations as you continue to refactor the Busso App.

In this chapter, you’ll learn:

  • Why you need a special tool to use Dagger in an Android app.
  • How Android Dagger works under the hood.
  • How to inject an Activity and a Fragment.
  • Which utility classes Android Dagger provides.
  • How to use @ContributesAndroidInjector.

Why Android is different for Dagger

In the previous chapters of this book, you converted Busso, which is an Android app, to Dagger. So if that worked, you might wonder why you’d need a specific Dagger Android library.

As you know, Android is a container that manages the lifecycle of its standard components, like Activitys, Services, ContentProviders and BroadcastReceivers. Because of that, Dagger can’t create an instance of a standard component like, for example, an Activity. That falls under the Android environment’s responsibilities.

You cannot use constructor injection in Android. Instead, you must write code like what you implemented in BusStopFragment.kt in the ui.view.busstop package of the app module:

class BusStopFragment : Fragment() {
  // ...
  override fun onAttach(context: Context) {
    context.activityComp
        .fragmentComponent()
        .inject(this)
    super.onAttach(context)
  }
  // ...
}

You had to write this code in all the Activitys and Fragments of the app. You did this exactly four times in Busso, and a larger project could need it even more often.

Sometimes, the code you need to write becomes very verbose, like what you have in SplashActivity.kt in the ui.view.splash package in app.

class SplashActivity : AppCompatActivity() {
  // ...
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    makeFullScreen()
    setContentView(R.layout.activity_splash)
    this.application.appComp
      .activityComponentBuilder()
      .activity(this)
      .build()
      .inject(this)
    splashViewBinder.init(this)
  }
  // ...
}

In a perfect world, injection should happen from the outside. In the previous example, SplashActivity should know nothing about the ApplicationComponent you use for the actual injection and it shouldn’t have any code with inject().

But, can you completely remove all that code? The answer is: no. What you can do is to make that code easier to write or, even better, ask Dagger to write it for you. That’s exactly what Dagger Android does.

Dagger Android gives you a way to generate the code that, in the first part of this book, you put in Injector implementations. To do this, you need some libraries and, of course, an annotation processor. That’s Dagger Android!

Installing Dagger Android dependencies

The first step toward using Dagger Android is to install its dependencies in the app module’s build.gradle by adding the following to it:

  // Dagger Android
  implementation "com.google.dagger:dagger-android:$dagger_version" // 1
  implementation "com.google.dagger:dagger-android-support:$dagger_version" // 2
  kapt "com.google.dagger:dagger-android-processor:$dagger_version" // 3

How Dagger Android works

To illustrate how Dagger Android works, you’ll migrate Busso’s MainActivity and SplashActivity injection targets following these steps:

Define an abstraction for the injectors

In Chapter 4, “Dependency Injection & Scopes”, you created the Injector<A> interface as the abstraction for any component with the responsibility of injecting objects into an injection target of type A. The interface looked like this:

interface Injector<A> {

  fun inject(target: A)

}
interface AndroidInjector<T> { // 1

  fun inject(T instance) // 2

  // 3
  interface Factory<T> {
    // 4
    fun create(@BindsInstance instance: T): AndroidInjector<T>
  }
}

Generating an injector for each injection target

In the previous step, you learned that Dagger Android provides an AndroidInjector<T> interface that all injectors need to implement to resolve the dependencies of an object of type T, which you called the injection target.

Refactoring MainActivity

Open ActivityComponent.kt in the di package of app and you’ll see the following:

@Subcomponent(
  modules = [ActivityModule::class]
)
@ActivityScope
interface ActivityComponent {

  fun inject(activity: SplashActivity) // HERE

  fun inject(activity: MainActivity) // HERE

  fun fragmentComponent(): FragmentComponent

  @Subcomponent.Builder
  interface Builder {
    fun activity(
      @BindsInstance activity: Activity // HERE
    ): Builder

    fun build(): ActivityComponent
  }
}
@Subcomponent(
  modules = [
    ActivityModule::class, // 1
  ]
)
@ActivityScope // 2
interface MainActivitySubcomponent : AndroidInjector<MainActivity> { // 3

  @Subcomponent.Factory // 4
  interface Factory : AndroidInjector.Factory<MainActivity> // 5
}

Refactoring SplashActivity

Next, you’ll do the same for SplashActivity. Just create a new package named splash in the existing di.activities and create SplashActivitySubcomponent.kt inside. Then, add the following code:

@Subcomponent(
  modules = [
    ActivityModule::class,
  ]
)
@ActivityScope
interface SplashActivitySubcomponent : AndroidInjector<SplashActivity> {

  @Subcomponent.Factory
  interface Factory : AndroidInjector.Factory<SplashActivity>
}

Cleaning up your code

Before continuing, you need to do some cleanup and refactoring. Specifically, you need to:

Simplifying the code

Supposing that Dagger already generated AndroidInjector<SplashActivity> and AndroidInjector<MainActivity> for you, how would you use them? This is the main advantage of Android Dagger — it allows you to replace all the boilerplate related to the inject() invocation you saw earlier with a simple instruction.

class MainActivity : AppCompatActivity() {

  @Inject
  lateinit var mainPresenter: MainPresenter

  override fun onCreate(savedInstanceState: Bundle?) {
    AndroidInjection.inject(this) // HERE
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    if (savedInstanceState == null) {
      mainPresenter.goToBusStopList()
    }
  }
}
class SplashActivity : AppCompatActivity() {
  // ...
  override fun onCreate(savedInstanceState: Bundle?) {
    AndroidInjection.inject(this) // HERE
    super.onCreate(savedInstanceState)
    makeFullScreen()
    setContentView(R.layout.activity_splash)
    splashViewBinder.init(this)
  }

  // ...
}

How AndroidInjection works

But how does AndroidInjection’s inject() work?

  fun inject(activity: Activity) { // 1
    val application = activity.application
    if (application is HasAndroidInjector) { // 2
      application.androidInjector().inject(activity) // 3
    } else {
      throw RuntimeException("Something wrong") // 2
    }
  }

Setting up Application for Dagger Android

As you just learned, to make Android Dagger work, you need to make your Application implement HasAndroidInjector so it can provide an implementation of AndroidInjector<Any> to use in your Activitys.

class Main : Application(), HasAndroidInjector { // 1

  @Inject
  lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Any> // 2

  override fun onCreate() {
    super.onCreate()
    DaggerApplicationComponent
      .factory()
      .create(this, BussoConfiguration)
      .inject(this)
  }
  // 2
  override fun androidInjector(): AndroidInjector<Any> {
    return dispatchingAndroidInjector
  }
}
@Module(
  includes = [
    LocationModule::class,
    NetworkModule::class,
    AndroidSupportInjectionModule::class // HERE
  ]
)
object ApplicationModule
@ApplicationScope
interface ApplicationComponent {

  fun activityComponentBuilder(): ActivityComponent.Builder // REMOVE!!
  // ...
}

Binding the injector to the specific injection target type

Dagger Android now knows which AndroidInjector<T> to create but it doesn’t know how to bind the result to a specific type. In Chapter 14, “Multibinding With Map”, you solved a similar problem using @ClassKey. You’ll do the same when you work with Dagger Android.

@Module(
  subcomponents = [MainActivitySubcomponent::class] // 1
)
interface MainActivityModule {

  @Binds
  @IntoMap
  @ClassKey(MainActivity::class) // 2
  fun bindMainActivitySubcomponentFactory(
    factory: MainActivitySubcomponent.Factory // 3
  ): AndroidInjector.Factory<*>
}

Adding the @Modules to ApplicationComponent

Now, according to what you did in step one above, you need to add MainActivityModule to the modules attribute of the @Component or @Subcomponent that you want to be MainActivitySubcomponent’s parent. This is ApplicationComponent.

@Module(
  subcomponents = [SplashActivitySubcomponent::class]
)
interface SplashActivityModule {

  @Binds
  @IntoMap
  @ClassKey(SplashActivity::class)
  fun bindSplashActivitySubcomponentFactory(
    factory: SplashActivitySubcomponent.Factory
  ): AndroidInjector.Factory<*>
}
@Component(
  dependencies = [NetworkingConfiguration::class],
  modules = [
    ApplicationModule::class,
    InformationPluginEngineModule::class,
    InformationSpecsModule::class,
    MainActivityModule::class, // HERE
    SplashActivityModule::class // HERE
  ]
)
@ApplicationScope
interface ApplicationComponent {
  // ...
}
error: [Dagger/MissingBinding] android.app.Activity cannot be provided without an @Inject constructor or an @Provides-annotated method.
public abstract interface ApplicationComponent {
                ^
      android.app.Activity is injected at
          com.raywenderlich.android.ui.navigation.di.NavigationModule.provideNavigator(activity)

Working around Dagger Android limitations

You’ve almost finished configuring Dagger Android for Busso’s Activitys, but you got an annoying error on the Navigator interface. This is a consequence of the limitation you read about earlier: Navigator needs an Activity but you have a MainActivity and a SplashActivity, instead.

Using qualifiers

Start by creating a new file named NavigatorModule.kt in the new di.navigator and add the following to it:

@Module
object NavigatorModule {

  @Provides
  @ActivityScope
  @Named("Main") // HERE
  fun providesMainActivityNavigator(owner: MainActivity): Navigator =
    NavigatorImpl(owner)

  @Provides
  @ActivityScope
  @Named("Splash") // HERE
  fun providesSplashActivityNavigator(owner: SplashActivity): Navigator =
    NavigatorImpl(owner)
}
class NavigatorImpl(private val activity: Activity) : Navigator { // HERE
  // ...
}

Adding Qualifiers

First, open ActivityModule.kt in di.activities in app and replace the existing NavigationModule with NavigatorModule, like this:

@Module(
  includes = [
    NavigatorModule::class // HERE
  ]
)
interface ActivityModule {
  // ...
}
class SplashViewBinderImpl @Inject constructor(
  @Named("Splash") private val navigator: Navigator // HERE
) : SplashViewBinder {
  // ...
}
class MainPresenterImpl @Inject constructor(
  @Named("Main") private val navigator: Navigator // HERE
) : MainPresenter {
  // ...
}
@FragmentScope
class BusStopListPresenterImpl @Inject constructor(
  @Named("Main") private val navigator: Navigator, // HERE
  private val locationObservable: Observable<LocationEvent>,
  private val bussoEndpoint: BussoEndpoint
) : BasePresenter<View, BusStopListViewBinder>(),
  BusStopListPresenter {
  // ...
}

Reviewing what you achieved

You’ve just migrated Busso’s Activitys to Dagger Android. It’s hard to believe, because the app doesn’t build successfully yet, but that’s only because you still need to migrate the Fragments. You’ll do that right after you summarize what you’ve done so far. You:

Injecting Fragments

Busso doesn’t build successfully yet because you still need to fix the Fragments for Dagger Android. The process is basically the same as you followed for the Activitys, but with some small but important differences.

Creating @Subcomponents and @Modules for Fragments

Create a new package named ui.fragments and move the existing FragmentModule.kt into it. Now, create a new package called di.fragments.busstop and create a new file named BusStopFragmentSubcomponent.kt in it. Finally, add the following content:

@Subcomponent(
  modules = [
    FragmentModule::class
  ]
)
@FragmentScope
interface BusStopFragmentSubcomponent : AndroidInjector<BusStopFragment> {

  @Subcomponent.Factory
  interface Factory : AndroidInjector.Factory<BusStopFragment> {

    override fun create(@BindsInstance instance: BusStopFragment): BusStopFragmentSubcomponent // HERE
  }
}
@Module(
  subcomponents = [BusStopFragmentSubcomponent::class]
)
interface BusStopFragmentModule {

  @Binds
  @IntoMap
  @ClassKey(BusStopFragment::class)
  fun bindBusStopFragmentSubcomponentFactory(
    factory: BusStopFragmentSubcomponent.Factory
  ): AndroidInjector.Factory<*>
}
@Subcomponent(
  modules = [
    FragmentModule::class
  ]
)
@FragmentScope
interface BusArrivalFragmentSubcomponent : AndroidInjector<BusArrivalFragment> {

  @Subcomponent.Factory
  interface Factory : AndroidInjector.Factory<BusArrivalFragment> {

    override fun create(@BindsInstance instance: BusArrivalFragment): BusArrivalFragmentSubcomponent
  }
}
@Module(
  subcomponents = [BusArrivalFragmentSubcomponent::class]
)
interface BusArrivalFragmentModule {

  @Binds
  @IntoMap
  @ClassKey(BusArrivalFragment::class)
  fun bindBusArrivalFragmentSubcomponentFactory(
    factory: BusArrivalFragmentSubcomponent.Factory
  ): AndroidInjector.Factory<*>
}
@Module(includes = [InformationPluginEngineModule.FragmentBindings::class]) // HERE
interface FragmentModule {
  // ...
}

Simplifying the Fragments’ injection

Fragments need code that’s similar to what you used for Activitys.

class BusStopFragment : Fragment() {
  // ...
  override fun onAttach(context: Context) {
    AndroidSupportInjection.inject(this) // HERE
    super.onAttach(context)
  }
  // ...
}
class BusArrivalFragment : Fragment() {
  // ...
  override fun onAttach(context: Context) {
    AndroidSupportInjection.inject(this) // HERE
    super.onAttach(context)
  }
  // ...
}

Setting up HasAndroidInjector for Fragments

inject() for AndroidSupportInjection follows the same logic you saw for the AndroidInjection and Activitys. To implement it, open MainActivity.kt in ui.view.main and apply the following changes:

class MainActivity : AppCompatActivity(), HasAndroidInjector { // 1

  @Inject
  lateinit var mainPresenter: MainPresenter

  @Inject
  lateinit var androidInjector: DispatchingAndroidInjector<Any> // 2

  override fun onCreate(savedInstanceState: Bundle?) {
    AndroidInjection.inject(this)
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    if (savedInstanceState == null) {
      mainPresenter.goToBusStopList()
    }
  }

  override fun androidInjector(): AndroidInjector<Any> = androidInjector // 3
}

Configuring the @Subcomponents’ relationships

For your last step, you need to set @Subcomponents related to Fragments as children of the one related to the MainActivity, which is the only one containing any Fragments.

@Subcomponent(
  modules = [
    ActivityModule::class,
    BusStopFragmentModule::class, // HERE
    BusArrivalFragmentModule::class // HERE
  ]
)
@ActivityScope
interface MainActivitySubcomponent : AndroidInjector<MainActivity> {

  @Subcomponent.Factory
  interface Factory : AndroidInjector.Factory<MainActivity>
}
Figure 16.1 — The Busso App
Reyoko 31.3 — Tgi Jaxne Azp

Using Dagger Android utility classes

Dagger Android’s goal was to help developers write less code, but frankly, at the moment, you’re not sure it did that. You wrote a lot of boilerplate in different files, to save other boilerplate in Activitys and Fragments.

Using DaggerApplication

Main, which is the Application for Busso, has quite a defined structure. It has to:

class Main : DaggerApplication() { // 1

  override fun applicationInjector(): AndroidInjector<out DaggerApplication> { // 2
    return DaggerApplicationComponent
      .factory()
      .create(this, BussoConfiguration)
  }
}
// ...
@ApplicationScope
interface ApplicationComponent : AndroidInjector<Main> { // 1

  @Component.Factory
  interface Factory { // 2

    fun create(
      @BindsInstance application: Application,
      networkingConfiguration: NetworkingConfiguration
    ): ApplicationComponent
  }
}

Using DaggerAppCompatActivity

Android Dagger provides the tools to reduce the boilerplate for Activitys as well. Open MainActivity.kt in ui.view.main and apply the following changes:

class MainActivity : DaggerAppCompatActivity() {

  @Inject
  lateinit var mainPresenter: MainPresenter

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    if (savedInstanceState == null) {
      mainPresenter.goToBusStopList()
    }
  }
}

Using @ContributesAndroidInjector with Activity

What you did with DaggerApplication and DaggerAppCompatActivity isn’t much compared to what you can achieve with @ContributesAndroidInjector.

@Module
interface ActivityBindingModule { // 1

  @ContributesAndroidInjector(  // 3
    modules = [
      ActivityModule::class,
      BusStopFragmentModule::class,
      BusArrivalFragmentModule::class
    ]
  )
  @ActivityScope // 4
  fun mainActivity(): MainActivity // 2

  @ContributesAndroidInjector(  // 3
    modules = [
      ActivityModule::class
    ]
  )
  @ActivityScope
  fun splashActivity(): SplashActivity // 2
}
@Component(
  dependencies = [NetworkingConfiguration::class],
  modules = [
    ApplicationModule::class,
    InformationPluginEngineModule::class,
    InformationSpecsModule::class,
    ActivityBindingModule::class // HERE
  ]
)
@ApplicationScope
interface ApplicationComponent : AndroidInjector<Main> {
  // ...
}

Using @ContributesAndroidInjector with Fragments

@ContributesAndroidInjector also works for Fragments. To see how, create a new file named FragmentBindingModule.kt in di with the following code:

@Module
interface FragmentBindingModule {

  @ContributesAndroidInjector(
    modules = [
      FragmentModule::class
    ]
  )
  @FragmentScope
  fun busStopFragment(): BusStopFragment

  @ContributesAndroidInjector(
    modules = [
      FragmentModule::class
    ]
  )
  @FragmentScope
  fun busArrivalFragment(): BusArrivalFragment
}
@Module
interface ActivityBindingModule {

  @ContributesAndroidInjector(
    modules = [
      ActivityModule::class,
      FragmentBindingModule::class // HERE
    ]
  )
  @ActivityScope
  fun mainActivity(): MainActivity
  // ...
}

Key points

  • Android is responsible for the lifecycle of its standard components, like Activitys, Services, BroadcastReceivers and ContentProviders. This prevents you from using constructor injection.
  • Dagger Android is Google’s first solution for reducing the boilerplate code you need to write to inject into Android standard components. The next solution is Hilt, which you’ll learn about in the rest of this book.
  • Using Dagger Android requires a deep knowledge of Dagger and, specifically, how @Subcomponents and multibinding work.
  • Dagger Android provides some utility classes to reduce the code you need to write.
  • @ContributesAndroidInjector allows you to automatically generate the code for AndroidInjector<T>’s @Subcomponent and @Module for a standard component.
Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a Kodeco Personal Plan.

Unlock now