Kriptofolio app series – Part 4: Dependency Injection with Dagger 2

Dependency injection will significantly improve your code by making it more modular, flexible and testable. Actually its name sounds more complicated than idea which stands behind it. In this part of the series we are going to learn about dependency injection and how we can implement it on “Kriptofolio” (previously “My Crypto Coins”) app. We are going to use Dagger 2 – the most popular open-source dependency injection framework for Android. This is valuable skill to have for creating modern apps, even learning curve is hard enough.

Series content

What is Dependency Injection?

To explain dependency injection first we have to understand what dependency means in respect of programming. A dependency is when one of the objects depends on the concrete implementation of another object. You can identify a dependency in your code whenever you instantiate an object within another. Let’s take a look at practical example.

class MyAppClass() {

    private val library: MyLibrary = MyLibrary(true)
    ...
}

class MyLibrary(private val useSpecialFeature: Boolean) {
    
    ...
}

As you see from this example your class MyAppClass will depend directly on concrete configuration and implementation of your library class MyLibrary. What if you would like one day to use third-party library instead? What if you would like to have another class where you would like to use exactly the same library configuration? Every time you will have to go deeply to your code, find exact place and change it. It’s just a few examples but the idea is that this tight coupling between the components of the application will make your development work harder as your project will grow. To avoid any problems let’s use dependency injection technique for loosening the coupling described.

class MyAppClass(private val library: MyLibrary) {
    
    ...
}

class MyLibrary(private val useSpecialFeature: Boolean) {
    
    ...
}

That’s it, that’s very primitive dependency injection. Instead of creating and configuring new MyLibrary class object inside your class MyAppClass, you just pass or inject it into constructor. So MyAppClass can be totally irresponsible for MyLibrary.

What is Dagger 2?

Dagger is a fully static, compile-time, open-source dependency injection framework for both Java and Android. In this article I will be talking about its second version which is maintained by Google, but its earlier version was created by Square. Dagger 2 is considered to be one of the most efficient dependency injection frameworks built to date. Actually If you as a user would compare Dagger 1, Dagger 2 and Dagger 2.10 you would find out that implementation is different and you need to relearn it each time as there was significant changes done by the authors. When writing this article I am using Dagger 2.16 version and we are going to focus only on it. As you now understand about dependency injection, our classes should not create or have its dependencies on it, but instead they need to get everything from outside. So when using Dagger 2, this framework will provide all the dependencies needed by generating a lot of boilerplate code for us. That generated code will be fully traceable and will mimic the code which user may write by hand. Dagger 2 is written in Java and the code generated by its annotation processor will be Java code too. However it works with Kotlin language without any problems or modifications as remember that Kotlin is fully inter-operable with Java. If compared to other for similar purpose created frameworks, Dagger 2 is less dynamic one. It works at compile time rather than at run-time with reflection. There is no reflection usage at all. All that means that this framework will require you more hard work to setup and especially learn it, but it will provide performance boost with compile time safety. It’s not the simplest, but instead most professional powerful dependency injection solution for Android.

Manual Dependency Injection without tools

Perhaps you have noticed in My Crypto Coins app source code from previous part that there is a piece of code for injecting objects without using any dependency injection tools. It works perfectly fine, and I think this solution would be good enough for such small app like this. Take a look at utilities package:

/**
 * Static methods used to inject classes needed for various Activities and Fragments.
 */
object InjectorUtils {

    private fun getCryptocurrencyRepository(context: Context): CryptocurrencyRepository {
        return CryptocurrencyRepository.getInstance(
                AppDatabase.getInstance(context).cryptocurrencyDao())
    }

    fun provideMainViewModelFactory(
            application: Application
    ): MainViewModelFactory {
        val repository = getCryptocurrencyRepository(application)
        return MainViewModelFactory(application, repository)
    }

    fun provideAddSearchViewModelFactory(
            context: Context
    ): AddSearchViewModelFactory {
        val repository = getCryptocurrencyRepository(context)
        return AddSearchViewModelFactory(repository)
    }
}

As you see this class will do all the work as it will create ViewModel factories for activities or fragments that require them.

/**
 * Factory for creating a [MainViewModel] with a constructor that takes a
 * [CryptocurrencyRepository].
 */
class MainViewModelFactory(private val application: Application, private val repository: CryptocurrencyRepository) : ViewModelProvider.NewInstanceFactory() {

    @Suppress("UNCHECKED_CAST")
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return MainViewModel(application, repository) as T
    }

}

Than you use InjectorUtils class like this where you need to get specific ViewModel factory:

/**
 * A placeholder fragment containing a simple view.
 */
class MainListFragment : Fragment() {

    ...

    private lateinit var viewModel: MainViewModel

    ...

    override fun onActivityCreated(savedInstanceState: Bundle?) {

        super.onActivityCreated(savedInstanceState)

        setupList()
        ...
    }

    ...

    private fun subscribeUi(activity: FragmentActivity) {

        // This is the old way how we were injecting code before using Dagger.
        val factory = InjectorUtils.provideMainViewModelFactory(activity.application)

        // Obtain ViewModel from ViewModelProviders, using parent activity as LifecycleOwner.
        viewModel = ViewModelProviders.of(activity, factory).get(MainViewModel::class.java)

        ...
    }

}

As you see our MainListFragment class don’t even know about CryptocurrencyRepository or AppDatabase as it gets only successfully constructed factory from InjectorUtils class. Actually this is one simple way to do it, however we are going to get rid of it and learn how to setup Dagger 2 tool for advanced dependency injection. If this app would expand in functionality and code, I don’t doubt we would start seeing benefits really fast of using professional dependency injection framework over manual solution. So let’s delete InjectorUtils class right now and learn how to setup Dagger 2 in My Crypto Coins app source code.

Dependency Injection for MVVM with Kotlin. How to setup Dagger 2 with ViewModels, Activities and Fragments?

Dagger 2 step by step setup on My Crypto Coins app project:

      1. To begin you should enable Kotlin own Annotation Processing Tool (kapt) and add special Dagger 2 dependencies.

        You can do this by adding these lines to your gradle file:

        apply plugin: 'kotlin-kapt' // For annotation processing
        
        ...
        
        implementation "com.google.dagger:dagger:$versions.dagger"
        implementation "com.google.dagger:dagger-android:$versions.dagger"
        implementation "com.google.dagger:dagger-android-support:$versions.dagger"
        kapt "com.google.dagger:dagger-compiler:$versions.dagger"
        kapt "com.google.dagger:dagger-android-processor:$versions.dagger"

        Kapt plugin will enable the compiler to generate stub classes required for interoperability between Java and Kotlin. For convenience we will define concrete Dagger 2 version in separate gradle file as we do that with all our dependencies.

        def versions = [:]
        
        versions.dagger = "2.16"
        
        ext.versions = versions

        To find out which latest version is available you should check releases at Dagger 2 official repository on Github.

      2. Create your application App class.

        Skip this if you already have this class set. After you done that, we will leave it as it is for a while, but come back later.

        class App : Application() {
        
            override fun onCreate() {
                super.onCreate()
            }
        
        }

        For My Crypto Coins app we already have created application class earlier.

      3. Next update your manifest file to enable your App class.

        Skip this if you already done that before.

        <manifest xmlns:android="http://schemas.android.com/apk/res/android"
            package="com.baruckis.mycryptocoins">
        
            <application
                android:name=".App"
                android:allowBackup="true"
                android:icon="@mipmap/ic_launcher"
                android:label="@string/app_name"
                ...

        For My Crypto Coins app we already set App class in manifest also earlier.

      4. Now let’s create new package called dependencyinjection.

        Here we are going to keep all the files related to Dagger implementation.

      5. Create AppModule class module which will provide dependencies all over your application.

        /**
         * AppModule will provide app-wide dependencies for a part of the application.
         * It should initialize objects used across our application, such as Room database, Retrofit, Shared Preference, etc.
         */
        @Module(includes = [ViewModelsModule::class])
        class AppModule() {
        
            @Singleton // Annotation informs Dagger compiler that the instance should be created only once in the entire lifecycle of the application.
            @Provides // Annotation informs Dagger compiler that this method is the constructor for the Context return type.
            fun provideContext(app: App): Context = app // Using provide as a prefix is a common convention but not a requirement.
        
            @Singleton
            @Provides
            fun provideCryptocurrencyRepository(context: Context): CryptocurrencyRepository {
                return CryptocurrencyRepository.getInstance(AppDatabase.getInstance(context).cryptocurrencyDao())
            }
        }

        As you see in order to create a Dagger module we need to annotate it with the special @Module annotation. Projects usually have multiple Dagger modules, and it is typical for one of them to provide app-wide dependencies. This AppModule will be used to initialize objects used across our application, such as Room database, Retrofit, Shared Preference, etc.
        As an example we could discuss very common scenario for AppModule to provide Context object in case we need it to get anywhere in our app. Let’s analyze the code how to do that. We need to use special Dagger annotation @Provides. It tells Dagger that the method provides a certain type of dependency, in our case, a Context object. So when somewhere in the app we request to inject a Context, AppModule is the place for Dagger where to find it. And it does not matter how our method name is, as Dagger cares only at the return type. It is only common practice to name method with provide prefix, but it can be anything you want. The @Singleton annotation which you see applied to the same method is not part of the Dagger annotations and is contained inside the javax package. This annotation tells Dagger that there should only be a single instance of that dependency. You don’t need to write the boilerplate code to check if another instance of the object is already available. When generating the code Dagger will handle all that logic for you because of this annotation.
        Notice that our AppModule includes another module ViewModelsModule, so let’s create it now.

      6. Create ViewModelsModule class module which will be responsible for providing ViewModels all over your application.

        /**
         * Will be responsible for providing ViewModels.
         */
        @Module
        abstract class ViewModelsModule {
        
            // We'd like to take this implementation of the ViewModel class and make it available in an injectable map with MainViewModel::class as a key to that map.
            @Binds
            @IntoMap
            @ViewModelKey(MainViewModel::class) // We use a restriction on multibound map defined with @ViewModelKey annotation, and if don't need any, we should use @ClassKey annotation provided by Dagger.
            abstract fun bindMainViewModel(mainViewModel: MainViewModel): ViewModel
        
            @Binds
            @IntoMap
            @ViewModelKey(AddSearchViewModel::class)
            abstract fun bindAddSearchViewModel(addSearchViewModel: AddSearchViewModel): ViewModel
        
            @Binds
            abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
        }

        This module uses Dagger 2 feature map multibindings. Using it we contribute objects of our choosing into a map that becomes injectable anywhere in our app. Using the combination of Dagger annotations @Binds, @IntoMap and our custom annotation @ViewModelKey(this one we are going to create), we create an entry inside our map with key MainViewModel::class and value MainViewModel instance. We bind specific factory with the help of some common ViewModelFactory class which we also need to create.

      7.  Create custom annotation class ViewModelKey.

        /**
         * An annotation class which tells dagger that it can be used to determine keys in multi bound maps.
         */
        @MustBeDocumented
        @Target(
                AnnotationTarget.FUNCTION,
                AnnotationTarget.PROPERTY_GETTER,
                AnnotationTarget.PROPERTY_SETTER
        )
        @Retention(AnnotationRetention.RUNTIME)
        @MapKey
        annotation class ViewModelKey(val value: KClass<out ViewModel>) // We might use only those classes which inherit from ViewModel.

        This class is used for binding ViewModels in the ViewModelsModule. The specific annotation @ViewModelKey represents the key of our map. Our key can be only a class that inherit from ViewModel.

      8. Create ViewModelFactory class.

        /**
         * Factory to auto-generate a Class to Provider Map.
         * We use Provider<T> to create an injectable object at a later time.
         */
        @Suppress("UNCHECKED_CAST")
        @Singleton
        class ViewModelFactory @Inject constructor(private val viewModelsMap: Map<Class<out ViewModel>,
                @JvmSuppressWildcards Provider<ViewModel>>) : ViewModelProvider.Factory {
        
            override fun <T : ViewModel> create(modelClass: Class<T>): T {
                var creator: Provider<out ViewModel>? = viewModelsMap[modelClass]
                if (creator == null) {
                    for (entry in viewModelsMap.entries) {
                        if (modelClass.isAssignableFrom(entry.key)) {
                            creator = entry.value
                            break
                        }
                    }
                }
                if (creator == null) {
                    throw IllegalArgumentException("Unknown model class $modelClass")
                }
        
                try {
                    return creator.get() as T
                } catch (e: Exception) {
                    throw RuntimeException(e)
                }
            }
        }

        This ViewModelFactory is utility class which basically helps you dynamically create ViewModels. Here you provide the generated map as argument and create() method will be able to pick the right instance from the map.

      9. Create ActivityBuildersModule class module.

        /**
         * All activities intended to use Dagger @Inject should be listed here.
         */
        @Module
        abstract class ActivityBuildersModule {
        
            @ContributesAndroidInjector(modules = [MainListFragmetBuildersModule::class]) // Where to apply the injection.
            abstract fun contributeMainActivity(): MainActivity
        
            @ContributesAndroidInjector
            abstract fun contributeAddSearchActivity(): AddSearchActivity
        }

        This module will responsible for constructing all your activities. It will generate AndroidInjector for all Activities defined in this class. Than objects can be injected into activities using AndroidInjection.inject(this) in the onCreate function from activity lifecycle. Notice that this module also uses another separate module responsible for fragments which we will create next.

      10. Create MainListFragmetBuildersModule class module.

        /**
         * All fragments related to MainActivity intended to use Dagger @Inject should be listed here.
         */
        @Module
        abstract class MainListFragmetBuildersModule {
        
            @ContributesAndroidInjector() // Attaches fragment to Dagger graph.
            abstract fun contributeMainListFragment(): MainListFragment
        }

        This module will build all your fragments related to MainActivity. It will generate AndroidInjector for all Fragments defined in this class, so that objects can be injected into Fragments using AndroidSupportInjection.inject(this) in the onAttach function from fragment lifecycle.

      11. Create AppComponent class interface.

        /**
         * Singleton component interface for the app. It ties all the modules together.
         * The component is used to connect objects to their dependencies.
         * Dagger will auto-generate DaggerAppComponent which is used for initialization at Application.
         */
        @Singleton
        @Component(
                modules = [
                    // AndroidSupportInjectionModule is a class of Dagger and we don't need to create it.
                    // If you want to use injection in fragment then you should use AndroidSupportInjectionModule.class else use AndroidInjectionModule.
                    AndroidSupportInjectionModule::class,
                    AppModule::class,
                    ActivityBuildersModule::class
                ]
        )
        interface AppComponent {
        
            @Component.Builder // Used for instantiation of a component.
            interface Builder {
        
                @BindsInstance // Bind our application instance to our Dagger graph.
                fun application(application: App): Builder
        
                fun build(): AppComponent
            }
        
            // The application which is allowed to request the dependencies declared by the modules
            // (by means of the @Inject annotation) should be declared here with individual inject() methods.
            fun inject(app: App)
        }

        Component is very important interface as it will enable all of the above to start working together by connecting objects to their dependencies. The Dagger will use this interface to generate the code necessary to perform the dependency injection. To create component class you will need to use Dagger annotation @Component which takes a list of modules as an input. Another annotation @Component.Builder allows us to bind some instance to component.

      12. Generate graph object.

        At this moment you have all your modules and your component setup so you can generate your graph object by selection Build -> Make Module inside your Android Studio IDE. This generation will be needed for our future step.

      13. Create Injectable interface.

        /**
         * It is just a plain empty marker interface, which tells to automatically inject activities or fragments if they implement it.
         */
        interface Injectable

        This also will be needed for our future steps. Injectable interface should be implemented by activities or fragments which we want to be injectable automatically.

      14. Create new helper class named AppInjector.

        /**
         * It is simple helper class to avoid calling inject method on each activity or fragment.
         */
        object AppInjector {
            fun init(app: App) {
                // Here we initialize Dagger. DaggerAppComponent is auto-generated from AppComponent.
                DaggerAppComponent.builder().application(app).build().inject(app)
        
                app.registerActivityLifecycleCallbacks(object : Application.ActivityLifecycleCallbacks {
                    override fun onActivityPaused(activity: Activity) {
        
                    }
        
                    override fun onActivityResumed(activity: Activity) {
        
                    }
        
                    override fun onActivityStarted(activity: Activity) {
        
                    }
        
                    override fun onActivityDestroyed(activity: Activity) {
        
                    }
        
                    override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle?) {
        
                    }
        
                    override fun onActivityStopped(activity: Activity) {
        
                    }
        
                    override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
                        handleActivity(activity)
                    }
                })
            }
        
            private fun handleActivity(activity: Activity) {
                if (activity is HasSupportFragmentInjector || activity is Injectable) {
                    // Calling inject() method will cause Dagger to locate the singletons in the dependency graph to try to find a matching return type.
                    // If it finds one, it assigns the references to the respective fields.
                    AndroidInjection.inject(activity)
                }
        
                if (activity is FragmentActivity) {
                    activity.supportFragmentManager.registerFragmentLifecycleCallbacks(object : FragmentManager.FragmentLifecycleCallbacks() {
                        override fun onFragmentCreated(fragmentManager: FragmentManager, fragment: Fragment, savedInstanceState: Bundle?) {
                            if (fragment is Injectable) {
                                AndroidSupportInjection.inject(fragment)
                            }
                        }
                    }, true)
                }
            }
        
        }

        It is just a simple helper class to avoid calling inject method on each activity or fragment.

      15. Setup App class which we already created before.

        class App : Application(), HasActivityInjector {
        
            @Inject // It implements Dagger machinery of finding appropriate injector factory for a type.
            lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Activity>
        
            override fun onCreate() {
                super.onCreate()
        
                // Initialize in order to automatically inject activities and fragments if they implement Injectable interface.
                AppInjector.init(this)
        
                ...
            }
        
        
            // This is required by HasActivityInjector interface to setup Dagger for Activity.
            override fun activityInjector(): AndroidInjector<Activity> = dispatchingAndroidInjector
        }

        Because application has activities we need to implement HasActivityInjector interface. If you see an error called out by Android Studio on DaggerAppComponent, it is because you have not generated a new file as it was pointed out in the previous step 12.

      16. Setup MainActivity to inject main ViewModel factory and add a support for fragment injections.

        // To support injecting fragments which belongs to this activity we need to implement HasSupportFragmentInjector.
        // We would not need to implement it, if our activity did not contain any fragments or the fragments did not need to inject anything.
        class MainActivity : AppCompatActivity(), HasSupportFragmentInjector {
        
            @Inject
            lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Fragment>
        
            @Inject
            lateinit var viewModelFactory: ViewModelProvider.Factory
            private lateinit var mainViewModel: MainViewModel
        
        
            override fun onCreate(savedInstanceState: Bundle?) {
                super.onCreate(savedInstanceState)
        
                // Obtain ViewModel from ViewModelProviders, using this activity as LifecycleOwner.
                mainViewModel = ViewModelProviders.of(this, viewModelFactory).get(MainViewModel::class.java)
        
                ...
            }
        
            ...
        
            override fun supportFragmentInjector(): AndroidInjector<Fragment> = dispatchingAndroidInjector
        
            ...
        }

        Because our activities have child fragments we need to implement HasSupportFragmentInjector interface. We also need this because we plan to make injections into our fragments. Our activity should not know about how it is injected and for that we should use AndroidInjection.inject(this) code line inside overriding onCreate() method. Calling inject() method will cause Dagger 2 to locate the singletons in the dependency graph to try to find a matching return type. However we don’t need to write any code here because it’s done for us by previously created AppInjector helper class which we initialized inside our application class.

      17. Setup MainListFragment to inject main ViewModel factory.

        /**
         * A placeholder fragment containing a simple view.
         */
        class MainListFragment : Fragment(), Injectable {
        
            ...
        
            @Inject
            lateinit var viewModelFactory: ViewModelProvider.Factory
            private lateinit var viewModel: MainViewModel
        
            ...
        
            override fun onActivityCreated(savedInstanceState: Bundle?) {
                super.onActivityCreated(savedInstanceState)
        
                ...
                subscribeUi(activity!!)
            }
        
            ...
        
            private fun subscribeUi(activity: FragmentActivity) {
        
                // Obtain ViewModel from ViewModelProviders, using parent activity as LifecycleOwner.
                viewModel = ViewModelProviders.of(activity, viewModelFactory).get(MainViewModel::class.java)
                
                ...
        
            }
        
        }

        Similar to activities, if we want our fragment to be injectable, than into its onAttach method we should write code AndroidSupportInjection.inject(this), but again this is job which is done by AppInjector helper, so we can skip that. Just notice that we need to add Injectable interface which we created earlier for helper to work.

Congratulations, we just implemented Dagger 2 in My Crypto Coins app project. Of course this article is just a quick guide to deploy Dagger 2 in your app straight away but not deep coverage of it. I recommend you to continue researching this topic over internet resources if you feel lost on the basics.

Repository

Checkout the source code of the updated “Kriptofolio” (previously “My Crypto Coins”) app on GitHub.

This post was also republished on Medium. Show your support by clicking the clap button 👏 on the story page here. 😇🙏

Share your thoughts

This site uses Akismet to reduce spam. Learn how your comment data is processed.