Web Analytics Made
Easy - StatCounter

Menu

About us Contact us

Dagger for Android: A Beginners Guide — Part 1

Tech 2019 / 09 / 04

Dagger for Android: A Beginners Guide — Part 1



Dagger has been around for a long time as the most popular dependency injection framework for Android. It’s been constantly evolving and adapting to developers’ needs ever since its birth leading to different ways of implementation varying in degrees of complexity and requiring a steep learning curve. But with the introduction of dagger-android, configuring Dagger within an Android project in 2019 is easier than ever.

Dagger was designed to be a cross-platform dependency injector for any Java-based project. It analyzes the dependencies, verifies them at compile-time and generates all the boilerplate code with a complete dependency graph under the hood — all at a cost of just a few steps to set up Dagger in the project. When used in an Android project, the process becomes a bit cumbersome at times. This has been made streamlined in the last few versions of dagger-android. With the removal of some interfaces and coalescing them into one, it has been made even simpler in the last update (2.24) which was released just last month (July ‘19).

This is intended to be a guide for beginners. In my opinion, the best way to learn Dagger is by working on a project and learn one thing at a time along the way. So I decided to write this three-part series based on the latest version of Dagger, not just for the absolute newbies, it’ll also work as a guide for my future self. I created this demo project for you (and me, of course) so that you can import it to Android Studio and set up Dagger all by yourself and learn interactively as we make progress through this article.

Special thanks to Atiqur Rahman for helping me out on the project.

Prerequisites

  1. The project has been written in Kotlin. If you’re not comfortable with it, you can still go through it and implement everything in Java on your own project.
  2. This project made use of ViewModels as part of the Android architecture components. If you haven’t played with them yet, I would highly recommend starting it right now.
  3. Network calls are performed using Retrofit. If you haven’t worked with Retrofit before, please take a look here to get an idea about what it does. That being said, knowing Retrofit is not crucial for this post to understand Dagger. All you need to know is that Retrofit uses an interface (that we define ourselves) in order to generate the implementation needed for network calls.
  4. While RxJava is not necessarily required to understand Dagger, it should be noted that RxJava call adapter factory is used with Retrofit in our project. This is not a prerequisite for this post. But if you haven’t used RxJava in your projects yet, I would highly recommend doing so.

What the app does

By the time we finish up everything, the app, when launched, will fetch 30 random images of cats through a network call and display them on a RecyclerView in MainActivity. If we click an item, a CatFragmentwill be loaded and a network call will be performed to fetch the URL of the selected cat image and load it in an ImageView.

The activity, fragment, recycler view, and their corresponding layouts, as well as the UI logic, ViewModels, repositories, and service, are already there except for the fact that it won’t work until we finish configuring Dagger. Some of the Dagger-specific classes have already been created in the dipackage. They are empty inside and we will fill them up step by step. By the time we’re done, I hope you’ll learn how to implement it in your own project. Without further ado, let’s dive right in.

Project overview

After cloning the project and importing in Android Studio, it would be better if you gloss over the packages in our project. The uipackage contains the activity, fragment, view model, adapter and a mysterious ViewModelFactory class which we will come to later. The service, repository, model, and Dagger classes are placed in their corresponding packages. For simplicity, I didn’t create any more package except utilthat you can completely forget for now and base that contains just a BaseViewModelclass which is not important for the purpose of our understanding.

Here’s the most important part — the direction of dependency. The dependency flow of our app is as follows:

Activity/Fragment > ViewModel > Repository > Service

You may also want to consider taking a look inside MainActivity.kt, it has a member dependency which is an instance of MainViewModel. Here’s how it looks:

class MainActivity : AppCompatActivity() {
  lateinit var mainViewModel: MainViewModel
  // …
}

The same goes for CatFragment:

class CatFragment : AppCompatDialogFragment() {
  lateinit var mainViewModel: MainViewModel
  //…
}

Note: using ViewModelproperty in the activity or fragment like this is an absolute no-no. We’re doing this for the sake of understanding Dagger and by the end of part 3, you’ll come to know why it’s not recommended and how to do it in a better way.

MainViewModeland CatDataRepositoryhave constructor parameters as their dependencies:

class MainViewModel
constructor(private val catRepository: CatRepository) : BaseViewModel()
class CatDataRepository
constructor(private val catService: CatService) : CatRepository

As you can see, MainActivityand CatFragmenthave a MainViewModeldependency each, which in turn has an abstract dependency on CatRepository.  CatRepositoryis an interface and is implemented by CatDataRepositoryCatDataRepositoryhas an abstract dependency on CatServicewhich is our Retrofit interface and is internally implemented by Retrofits auto-generated class.

That’s all you need to know for now and don’t care about the implementation details of any class. We just want mainViewModelproperty in our MainActivityto be set and we want Dagger to satisfy all the dependencies that come with it. First things first, let’s set up Dagger in our project.

Adding Gradle dependencies

If you take a look at our app/build.gradlefile, you can see the following dependencies:

final dagger_version = '2.24'
implementation "com.google.dagger:dagger:$dagger_version"
kapt "com.google.dagger:dagger-compiler:$dagger_version"
implementation "com.google.dagger:dagger-android:$dagger_version"
implementation "com.google.dagger:dagger-android-support:$dagger_version"
kapt "com.google.dagger:dagger-android-processor:$dagger_version"

Here I used version 2.24which is the latest at the time of writing this article. You can find the latest version of Dagger here. All the other dependencies included in the file have little to do with understanding Dagger. So, let’s move on.

Creating a custom Application class

First thing you need to do when setting up Dagger in your project is create a custom Applicationclass, if not there’s one already, and make sure it’s registered in AndroidManifest.xml. In our project, there’s already an Application class, PurrfectApp, that I’ve written for you. Just add this field inside of it:

@Inject
lateinit var dispatchingAndroidInjector: 
DispatchingAndroidInjector<Any>

The @Injectannotation used here (and this is true for any other fieldannotated with it) is the way you tell Dagger to instantiate an object of its type and set the field to that object. This is called field injection. The DispatchingAndroidInjectorclass will be instantiated and the field will be set by Dagger itself. This class takes care of injecting objects into Android classes such as activities, fragments etc. and is used internally by Dagger.

Now implement HasAndroidInjectorand return dispatchingAndroidInjectorfrom the overridden androidInjector()function. Once you do it, PurrfectAppshould look like this:

class PurrfectApp : Application(), HasAndroidInjector {
  
  // 1
  @Inject
  lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Any>
  
  override fun androidInjector(): AndroidInjector<Any> {
    // 2
    return dispatchingAndroidInjector
  }
  
  override fun onCreate() {
    super.onCreate()
  }
}

Creating the app component

Time to create a Component. It’s an interface extending AndroidInjectorthat Dagger implements to generate a class that contains all the stuff needed for dependency injection from the modules (more on this later) that we install into it. You can name it anything you want (but do make it meaningful) as long as you annotate it with @Component. Also, install AndroidSupportInjectionModulein it. Look at the code snippet from our AppComponent.ktbelow to see how I’ve already done all this for you:

@Component(
  modules = [
    AndroidSupportInjectionModule::class
  ]
)
interface AppComponent : AndroidInjector<PurrfectApp>

This component is our key to injecting objects into our Android components (i.e.- activities, fragments etc.). It has a reference to our Applicationclass, hence references to the activities, fragments etc. as well, and is created along with a complete object graph when PurrfectAppis instantiated by the system when we launch the app.

Now build the project and let Dagger generate the implementation of AppComponent. The name of the generated class will be the interface name prefixed with Dagger —DaggerAppComponentin our case.

Now when the build is finished, after the super.onCreate() line in onCreate()of PurrfectApp, call DaggerAppComponent.create().inject(this).

class PurrfectApp : Application(), HasAndroidInjector {
  //…
  override fun onCreate() {
    super.onCreate()
    DaggerAppComponent.create().inject(this)
  }
}

Now run the app and let it go into a crash to see the logcat message:

kotlin.UninitializedPropertyAccessException: lateinit property mainViewModel has not been initialized

This happened because we haven’t told Dagger that our MainActivity is injectable like PurrfectAppclass. But instead of creating another component, we have to create a subcomponent for MainActivity.

Creating subcomponents

Create an abstract class annotated with @Moduleand name it anything you want to create a Dagger module. Inside the class, declare an abstract function annotated with @ContributesAndroidInjectorthat has a return type of the activity you’re binding to the subcomponent. In our project, we named the module ActivityBindingModule. Here’s how it looks:

@Module
abstract class ActivityBindingModule {
  @ContributesAndroidInjector
  abstract fun contributeMainActivity(): MainActivity
}

Note that the name of the function is arbitrary and isn’t called at runtime. It’s the return type that matters which is the activity you’re making the subcomponent for.

Now install this module in the application component:

@Component(
  modules = [
    AndroidSupportInjectionModule::class,
    // install the module here
    ActivityBindingModule::class
  ]
)
interface AppComponent : AndroidInjector<PurrfectApp>

In the onCreate()function of your MainActivityclass, call AndroidInjection.inject(this)before the super.onCreate()call.

class MainActivity : AppCompatActivity() {
  //…
  override fun onCreate(savedInstanceState: Bundle?) {
    AndroidInjection.inject(this)
    super.onCreate(savedInstanceState)
    //…
  }
  //…
}

Now you can inject any field you want in our MainActivityusing the @Injectannotation:

class MainActivity : AppCompatActivity() {
  @Inject
  lateinit var mainViewModel: MainViewModel
  //…
}

But if you build the project now, you will get a compilation error that says:

error: [Dagger/MissingBinding] com.avi5hek.purrfectapp.ui.MainViewModel cannot be provided without an @Inject constructor or an @Provides-annotated method.

This is Dagger’s way of telling you that it doesn’t know how to instantiate our MainViewModelclass. We need to provide the dependency so that Dagger knows where to find it.

Providing dependencies

One way we can accomplish this is called constructor injection. Go to MainViewModelclass and insert @Injectbetween the class name and its constructor, like this:

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

This way Dagger knows where to get the instance of the requested type (i.e.- MainViewModel). But since MainViewModelhas its own dependency on CatRepository, we need to provide it too. CatRepositoryis implemented by CatDataRepository, let’s do the same for this class as well.

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

Now Dagger knows how to instantiate CatDataRepository, but MainViewModel is asking for CatRepositoryand Dagger isn’t so sure about which of its instances should be used to resolve the dependency. Let’s tell Dagger that it should use CatDataRepositorywhen we need a CatRepositoryinstance. To do that, create a new abstract module class just like before and put inside an abstract function that takes CatDataRepository as a parameter and returns CatRepository. Annotate it with @Bindsso that Dagger knows that it should bind CatRepository to CatDataRepository. Go to our CatModule.ktfile and add this snippet:

@Module
abstract class CatModule {
  @Binds
  abstract fun bindCatRepository(catDataRepository: CatDataRepository): CatRepository
}

Just like before, the name of the function doesn’t matter. But name it meaningfully for better readability. Don’t forget to install the newly created module in the application component:

@Component(
  modules = [
    AndroidSupportInjectionModule::class,
    ActivityBindingModule::class,
    // install our new module
    CatModule::class
  ]
)
interface AppComponent : AndroidInjector<PurrfectApp>

 

Now, if you build the project, you will get a different compilation error. This time CatServicecannot be provided without an @Provides-annotated method. But unlike CatRepository, this interface is supposed to be implemented by Retrofit’s generated class behind the scene. Since we cannot inject its constructor as we did before, we need to instantiate the class ourself and provide it to Dagger. We can get the object of that class by calling create()on the Retrofitinstance and bind our Service interface to that. We can do it by using a static @Providesfunction in our CatModuleclass:

@Module // Note this annotation added before the companion object
companion object {
  //…
  @JvmStatic
  @Provides
  fun provideCatService(retrofit: Retrofit): CatService {
    return retrofit.create(CatService::class.java)
  }
}

This way, when Dagger needs a CatServiceinstance, it will get it from the return statement of this function. But as you can see, this function has its own dependency on Retrofit. You can provide it exactly the way we did for the CatService instance — declare a static function with @Providesannotation right below bindCatRepository(), build Retrofitinstance and return it. But I would recommend creating a different module for network-related dependencies as we did in our project with the NetworkModule. I’ve already written the code in this module for you. You don’t need to understand everything written there, just take a quick look, you can see some other examples of@Providesfunctions. Don’t forget to install this module in the AppComponent.

Pro tip: keeping different types of dependencies in separate modules is a good practice in terms of separation of concern.

Note that, the order of the provider functions is totally arbitrary — you don’t need to keep a strict order of function declarations for the dependencies since Dagger will create the graph in the correct order no matter how you organize the @Providesor @Bindsfunctions.

If you build the project, the compilation will fail again. Because Retrofitdepends on SchedulerProviderwhich has a binder function declared in AppModulefor the sake of separation of concern. Add @Injectto the AndroidSchedulerProviderconstructor and install the module into AppComponent.

Now if you build and run the app, it will show you a list of cute cat images which means our dependencies have been properly resolved except for the fact that when you click an image from the list, it will cause a crash. In the logcat you’ll see something like this:

kotlin.UninitializedPropertyAccessException: lateinit property mainViewModel has not been initialized

at com.avi5hek.purrfectapp.ui.CatFragment.onViewCreated(CatFragment.kt:36)

We need to inject MainViewModelinto CatFragmentas well. For that, we have to make CatFragmentinjectable by creating a subcomponent for it, just like we did for the activity before. To spare you the trouble of repeating the subcomponent creation procedure, I’ve already written the code in MainFragmentBindingModule. You just need to install it into AppComponentso that it looks like this:

@Component(
  modules = [
    AndroidSupportInjectionModule::class,
    ActivityBindingModule::class,
    CatModule::class,
    NetworkModule::class,
    AppModule::class,
    MainFragmentBindingModule::class
  ]
)
interface AppComponent : AndroidInjector<PurrfectApp>

Unlike activities, where we call AndroidInjection.inject(this)in onCreate(), we call AndroidSupportInjection.inject(this)from the onAttach()function of the fragment that we want to inject before the super.onAttach()call. Also, annotate mainViewModelproperty with @Inject.

class CatFragment : AppCompatDialogFragment() {
  // 2
  @Inject
  lateinit var mainViewModel: MainViewModel
  override fun onAttach(context: Context?) {
    // 1
    AndroidSupportInjection.inject(this)
    super.onAttach(context)
  }
  //…
}

Note: call AndroidInjection.inject(this)before super.onAttach()if you’re not using Fragmentfrom the support library in your own project.

Now run the app and see the magic. Everything works fine, right? Not so fast, chief! Every time mainViewModelfield is set in our activity or fragment, Dagger instantiates a new object of it. Not just the MainViewModel, this is true for the whole dependency chain — each and every object in the chain is instantiated over and over again! This is bad because the cost of object instantiation is high and if the project gets bigger, which every project eventually does, the runtime overhead of this implementation will become huge.

This can be fixed by using scopes. For this part of the guide, we haven’t used anything from the scopepackage. This will be discussed in part 2. There’s also ViewModelKeyand ViewModelFactoryfiles that we haven’t used yet. Let’s save them for the third part of the series.

In your projects, you can bind/provide any number of dependencies in as many modules you like, just make sure the modules are installed in the appropriate component/subcomponent such that no higher-level instance depends on a lower level one. For instance, objects provided in the module installed in a subcomponent can depend on parent component/subcomponent modules, not the other way around. We’ll discuss this in part 2 where we’ll deal with scoped bindings.

Please feel free to share your thoughts. You can reach out to me on Twitter: @Avishek_Khan. You might also want to check out some tech posts on our blog. Until next time, happy injecting!

Related Post :

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

You have ideas, We have solutions.

CONTACT US!