Dagger – A New Dependency Injection for JAVA And Android

Google Dagger

If we were to simply put the core principle of why Dagger was made, it would be

| Classes should not instantiate their dependencies

What do we mean by that?

Given that your ViewModel/Presenter needs access to a database, this database should be created somewhere else and pass it in your ViewModel/Presenter’s constructor. Instead of giving your ViewModel/Presenter the responsibility of creating it.

Instead of doing this:

class MyViewModel : ViewModel() {
    private lateinit var database: Database
 
    init {
      database = Database()
    }
}
With Dagger, you can do this:
class MyViewModel @Inject constructor(database: Database) : ViewModel() {}
Why?
  • If multiple classes needs this database, you don’t want to instantiate it multiple times for each class that needs it. That would consume a lot of resources when instead you need only one.
  • These dependencies usually take some time to setup and might block your thread so you offload them somewhere else.
  • Allows your classes to be testable in which you can pass mock objects in the constructor

 

Classes and Dependencies are basically the same thing – a class.

Both differ only in their role.

So here’s the difference between Classes and Dependencies as both will be heavily used throughout this guide.

Class – a Java/Kotlin class that may or may not need dependency(s).

Dependency – is also a Java/Kotlin class, but other classes “depend” on them as well. Examples of dependencies are Database class, Shared Preferences, Repository classes, and etc.


Glossary

Below are terms that we need to know first before we jump right in. Come back here from time to time if you forgot something while reading through the guide.


@Module – classes annotated with this annotation are responsible for creating dependencies that other classes need.

@Module
class StorageModule {
    @Provides
    @Singleton
    fun providesSharedPreferences(application: Application): SharedPreferences {
        return PreferenceManager.getDefaultSharedPreferences(application.applicationContext)
    }
}


@Component – interfaces annotated with this annotation lists the classes that need dependencies created by modules. Modules to be used are passed to the modules parameter of the annotation.

@Singleton
@Component(modules = [StorageModule::class, ...])
interface AppComponent {
...
    fun inject(app: App)
}
@Scope – scope annotations are used to tell Dagger how long this dependency lives. Other scopes in the baseplate are @ActivityScope and @FragmentScope.
@Provides
@Singleton
fun providesSharedPreferences(application: Application): SharedPreferences {
    return PreferenceManager.getDefaultSharedPreferences(application.applicationContext)
}
@Inject – tells Dagger that this class’ constructor/field needs a dependency(s).
class MyViewModel @Inject constructor(database: Database) : ViewModel() {}
@Provides – used to annotate methods in module classes that tell dagger this method “provides” a dependency and this is how the dependency is created.
@Provides
@Singleton
fun providesSharedPreferences(application: Application): SharedPreferences {
    return PreferenceManager.getDefaultSharedPreferences(application.applicationContext)
}
@Binds – if a method of your module class just returns the injected parameter, use this annotation as it’s much more optimized and generates less code. So instead of this:
@Module
class AppModule {
    @Singleton
    @Provides
    fun providesApplicationContext(app: App) : Context {
        return app
    }
}
In Dagger, you can do this:
@Module
abstract class AppModule {
    @Singleton
    @Binds
    abstract fun providesApplicationContext(app: App) : Context
}
@ContributesAndroidInjector – a helper annotation in dagger-android that is used to generate the necessary subcomponents for your Activities, Fragments, Services, or Broadcast Receivers instead of creating a lot of boilerplate code. We will learn more about this later in the guide.
@Module
abstract class ActivityBuilder {
    @ActivityScope
@ContributesAndroidInjector(modules = [MainRepositoryModule::class])
    abstract fun contributeMainActivity(): MainActivity
}
Exploring the Android Baseplate
When you generate a project using the baseplate the very first thing that you should do is open di folder.
Android Baseplate - Dagger

Components

Our main component is called AppComponent. This interface is the entry point to all our dependencies. Open AppComponent interface and let’s examine the parts one by one.

@Singleton

A scope provided already by the javax.inject package. This scope is typically used for dependencies that are mostly used by other classes (e.g. Database, SharedPreferences, etc.). It tells dagger that dependencies annotated with this scope (check methods in StorageModule or NetworkModule) will be destroyed when the app is destroyed.

@Component(
modules = [
        AndroidSupportInjectionModule::class,
        StorageModule::class,
        NetworkModule::class,
        ActivityBuilder::class,
        ViewModelModule::class
    ]
)
All of these modules except AndroidSupportInjectionModule (we’ll get to this later) are ours. Please read what modules are in the Glossary section.
interface AppComponent {
@Component.Builder
    interface Builder {
        @BindsInstance
        fun application(application: Application): Builder
 
        fun build(): AppComponent
    }
...
}
This part simply tells you that the Application class should be available for all modules of this component. Check out the StorageModule class and you’ll see that Application is passed as an injected parameter that is used to provide the SharedPreferences.
interface AppComponent {
...
fun inject(app: App)
}
This method is called to allow us to inject dependencies provided by AndroidSupportInjectionModule (try checking out the class and open AndroidInjectionModule) to our App class.

 

Modules

 

StorageModule Class
@Module
class StorageModule {
 
    companion object {
        const val KEY_WHATS_NEW = "landing:whats:new"
    }
 
@Provides
    @Singleton
    fun providesSharedPreferences(application: Application): SharedPreferences {
        return PreferenceManager.getDefaultSharedPreferences(application.applicationContext)
    }
}

We get the default shared preferences used by our app and provide it to other classes. Since this method is annotated with @Singleton same as AppComponent means that this will live until our app is destroyed.

 

NetworkModule Class

@Module
class NetworkModule {
    ...
 
    @Provides
    @Singleton
    fun providesOkHttpClient(): OkHttpClient {
        val logging = HttpLoggingInterceptor()
        logging.level = HttpLoggingInterceptor.Level.BODY
        return OkHttpClient.Builder()
                .addInterceptor(logging)
                .build()
    }
 
    @Provides
    @Singleton
    fun providesGson(): Gson {
        return GsonBuilder()
                .setLenient()
                .create()
    }
 
    @Provides
    @Singleton
    fun providesRetrofit(okHttpClient: OkHttpClient, gson: Gson): Retrofit {
        return Retrofit.Builder()
                .baseUrl(String.format(ENDPOINT_FORMAT, BASE_URL, API, VERSION))
                .addConverterFactory(ScalarsConverterFactory.create())
                .addConverterFactory(GsonConverterFactory.create(gson))
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .client(okHttpClient)
                .build()
    }
 
    @Provides
    @Singleton
    fun providesBaseplateApiServices(retrofit: Retrofit): BaseplateApiServices =
            retrofit.create(BaseplateApiServices::class.java)
 
}

Everything in here are dependencies used for network related tasks. Since they are also annotated with @Singleton, they will live until the app is destroyed.

 

ViewModelModule Class

@Suppress("unused")
@Module
abstract class ViewModelModule {
 
@Binds
    @IntoMap
    @ViewModelKey(ScoreViewModel::class)
    abstract fun bindScoreViewModel(viewModel: ScoreViewModel): ViewModel
 
 
    @Binds
    abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
 
}
What does this code does?
@Binds
@IntoMap
@ViewModelKey(ScoreViewModel::class)
abstract fun bindScoreViewModel(viewModel: ScoreViewModel): ViewModel

It will basically store this ScoreViewModel inside a Map generated by Dagger and set the key to ScoreViewModel class which will be used by our ViewModelFactory class (open this class and you’ll see all our ViewModels in a Map passed in the constructor).

If you create ViewModels with parameters, you need to create a ViewModelProvider.Factory for each of your ViewModel and that would be too repetitive. With this approach, we just create a generic factory and let dagger inject these ViewModels for us.

If you’ll create new ViewModel classes, don’t forget to add them in here. To know the difference between @Binds and @Provides, check out the Glossary and Trivia section.

 

AppModule Class

@Module
abstract class AppModule {
 
@Singleton
    @Binds
    abstract fun providesApplicationContext(app: App) : Context
}

Usually when you need the Context class or closely related to it you put it inside this module. That’s pretty much what this module is all about.

 

ActivityBuilder Class

Go to di -> builders.

@Module
abstract class ActivityBuilder {
    @ActivityScope
    @ContributesAndroidInjector(modules = [(MainRepositoryModule::class)])
    abstract fun contributeMainActivity(): MainActivity
}

@ActivityScope is a scope that we’ve made – see `di → scopes`. We use this scope for dependencies that we inject to our Activities that will get destroyed when the Activity is destroyed.

@ContributesAndroidInjector is a helper annotation by dagger-android to generate the necessary subcomponents for our Activities.

Instead of doing:

  1. Create an interface annotated with @Subcomponent that implements AndroidInjector<MainActivity>
  2. Inside the interface created in #1, add a @Subcomponent.Builder abstract class that extends AndroidInjector.Builder<MainActivity>
  3. Create a module that defines all the subcomponents for each Activity

You do this:

@Subcomponent(modules = MainRepositoryModule::class, ...)
public interface MainActivitySubcomponent extends AndroidInjector<MainActivity> {
  @Subcomponent.Builder
public abstract class Builder extends AndroidInjector.Builder<MainActivity> {}
}
 
@Module(subcomponents = MainActivitySubcomponent::class, ...)
abstract class ActivityBuilder {
@Binds
@IntoMap
@ClassKey(MainActivity::class)
    abstract AndroidInjector.Factory<?>
      bindMainActivityInjectorFactory(MainActivitySubcomponent.Builder builder)
}

If you add more activities it will lead to a LOT of boilerplate code because you’ll have to create a subcomponent for each Activity/Fragment that you create.

That’s where @ContributesAndroidInjector comes in. It will generate all of this for us. Just make sure ActivityBuilder is listed in your AppComponent’s modules.

If you create a new Activity, always add them here if they need dependencies.

MainRepositoryModule Class

Go to `features → main`.

@Module
class MainRepositoryModule {
@ActivityScope
    @Provides
    fun providesScoreRemoteSource(): ScoreRemoteSource = ScoreRemoteSource()
 
    @ActivityScope
    @Provides
    fun providesScoreRepository(remote: ScoreRemoteSource): ScoreRepository =
        ScoreRepository(remote)
}
In ActivityBuilder class, MainRepositoryModule is provided to MainActivity with an @ActivityScope. Annotate your dependencies inside MainRepositoryModule with @ActivityScope as well so that they will get destroyed when MainActivity gets destroyed.

Note: @ActivityScope doesn’t mean that these dependencies will be shared with different activities. It means that these dependencies will live for this specific activity only.For example, ActivityA & ActivityB depends on ScoreRemoteSource. When you go to ActivityB, ActivityA gets destroyed ScoreRemoteResource gets destroyed and then it will spawn a new ScoreRemoteSource for ActivityB.

If you want ScoreRemoteSource to live outside of Activities, put them in NetworkModule or create a new module(suggested) and annotate the dependency with @Singleton – which is the scope used for AppComponent that lives until the app is destroyed.

 

You usually create dedicated modules to each of your Activities just like MainRepositoryModule but you can also share modules to other Activities as well when they just depend on the same dependencies.

Injectable Interface

Under `di` folder, open Injectable interface.

/**
 * Marks an activity / fragment injectable.
 */
interface Injectable

If your Fragment doesn’t extend BaseFragment, implement this interface.
If your Activity doesn’t extend DaggerBaseActivity, BaseActivity, AppCompatActivity, or FragmentActivity, implement this interface.

This interface will be used in the next section.

AppInjector

Under `di` folder, open AppInjector object.

To create a Singleton in Kotlin, just use object. It’s a shortcut to creating Singletons in Kotlin.

Let’s examine this class one by one.

fun init(app: App) {
DaggerAppComponent
.builder()
.application(app)
          .build()
.inject(app)
  ...
}

Remember the @Component.Builder part in Components section? This is how it will be used.

  1. DaggerAppComponent – is our AppComponent generated by dagger with the necessary code.
  2. .builder() – the Builder interface annotated with @Component.Builder being called.
  3. .application(app) – the method declared in the Builder interface passing our App class to be used by all of our modules.
  4. .inject(app) – the method in our AppComponent that tells Dagger to allow injection of dependencies to our App class
fun init(app: App) {
    ...
app.registerActivityLifecycleCallbacks(object : Application.ActivityLifecycleCallbacks {
        override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
            handleActivity(activity)
        }
...
    })
}

We listen to Activities being created and handle them accordingly described below.

private fun handleActivity(activity: Activity) {
if (activity is HasSupportFragmentInjector || activity is HasActivityInjector) {
        AndroidInjection.inject(activity)
    }
...
}
Instead of adding AndroidInjection.inject(this) in every onCreate method of your Activities. We just let them implement HasSupportFragmentInjector if our Activity has fragment(s) or HasActivityInjector if they don’t have fragments. We’ll learn what AndroidInjection.inject(..) after we examine handleActivity method.
private fun handleActivity(activity: Activity) {
    ...
if (activity is FragmentActivity) {
        activity.supportFragmentManager
.registerFragmentLifecycleCallbacks(
                        object : FragmentManager.FragmentLifecycleCallbacks() {
                            override fun onFragmentCreated(
                                    fm: FragmentManager,
                                    f: Fragment,
                                    savedInstanceState: Bundle?
                            ) {
                                if (f is Injectable) {
                                    AndroidSupportInjection.inject(f)
                                }
                            }
                        }, true
                )
    }
}

The same with listening to Activities being created, we listen to fragments being created. That’s where the Injectable interface comes in.

If a fragment implements Injectable interface, we call `AndroidSupportInjection.inject(…)`.

AndroidInjection.inject(…) and AndroidSupportInjection.inject(…) basically means:

| Hey Dagger! Please allow these classes which are created by the Android framework to support injection of dependencies. So that we can now use @Inject inside these classes.

You can manually call these methods in your Activity’s onCreate or Fragment’s onAttach methods or you can use this helper class. It’s up to your preference if you prefer to explicitly call these methods or just let callbacks do the work.

App Class

Under your package name, open App.

class App : Application(), HasActivityInjector {
  ...
    override fun onCreate() {
        super.onCreate()
AppInjector.init(this)
    }
}
This is the only place where the init method of your AppInjector class gets called and passing this App class.
class App : Application(), HasActivityInjector {
    @Inject
    lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Activity>
 
    override fun activityInjector() = dispatchingAndroidInjector
    ...
}

Since we just let our App class do the `Android.injection(…)` using the AppInjector class, we implemented the HasActivityInjector interface with a method that needs a class that implements AndroidInjector<Activity> which is DispatchingAndroidInjector<Activity> injected in our App class.

The AndroidSupportInjectionModule provides them already for us. Check the Components section. Try opening the class also and examine the code.

Putting It All Together

  1. At compile time, dagger and dagger-android generates the necessary classes/code needed to wire things up for dependency injection (Components, Modules, Scopes, etc.).
  2. App’s `onCreate()` method gets called and calls AppInjector’s `init(…)` method passing in our App class.
  3. Uses DaggerAppComponent to setup our dependency graph.
  4. Listen to activities or fragments being created.
  5. Enable field injection for this activities or fragments.
  6. Inject the necessary dependencies to your classes’ constructors or fields.
  7. Create and destroy dependencies based on the scopes.

That’s pretty much the summary of the “magic” of Dagger.

I Don’t Have Time to Learn This

If you don’t have time to learn this and want to proceed right away. Here are the necessary steps that you should do to not encounter errors and wire things up properly.

Note: Everything in this section are all examples. Please name your classes, interfaces or variables properly.

Creating A New Activity

1. Be sure to add it in ActivityBuilder

@Module
abstract class ActivityBuilder {
    @ActivityScope
    @ContributesAndroidInjector(modules = [MyRepositoryModule::class])
    abstract fun contributeMyActivity(): MyActivity
}

Note: If you created a module for this Activity, pass it in @ContributesAndroidInjector modules parameter.

2. Make sure your Activity extends DaggerBaseActivity

class MyActivity : DaggerBaseActivity() {
Creating a New ViewModel for an Activity
1. Be sure to add it in ViewModelModule
@Suppress("unused")
@Module
abstract class ViewModelModule {
 
@Binds
    @IntoMap
    @ViewModelKey(MyViewModel::class)
    abstract fun bindMyViewModel(viewModel: MyViewModel): ViewModel
 
    ...
}
2. Always add @Inject in your constructor whether you need dependencies or not
class MyViewModel @Inject constructor(private val sharePref: SharedPreferences) :
        ViewModel() {
class MyViewModel @Inject constructor() : ViewModel() {
3. In your activity, be sure to instantiate your ViewModel
viewModel = ViewModelProviders.of(this, viewModelFactory).get(MyViewModel::class.java)

 

Creating A New Dependency

1. In your Activity’s dedicated module, instantiate it there

@Module
class MyRepositoryModule {
 
@ActivityScope
    @Provides
    fun providesMyRemoteSource(): MyRemoteSource = MyRemoteSource()
 
    @ActivityScope
    @Provides
    fun providesMyRepository(remote: MyRemoteSource): MyRepository =
        MyRepository(remote)
}
2. Inject it to your Activity’s ViewModel
class MyViewModel @Inject constructor(private val myRepository: MyRepository) :
        ViewModel() {

Creating A New Fragment

1. Make sure your fragment extends BaseFragment

class MyFragment : BaseFragment() {
2. Create a module class for the Activity that uses this fragment
@Module
abstract class MyActivityBuilder {
@FragmentScope
@ContributesAndroidInjector(modules = [MyFragmentRepositoryModule::class])
abstract fun contributeMyFragment(): MyFragment
}

3. In your ActivityBuilder, add this to the list of modules for the Activity(s) that uses this fragment

@Module
abstract class ActivityBuilder {
    @ActivityScope
    @ContributesAndroidInjector(modules = [MyRepositoryModule::class, MyActivityBuilder::class])
    abstract fun contributeMyActivity(): MyActivity
}

Creating a New ViewModel for a Fragment

1. Be sure to add it in ViewModelModule

@Suppress("unused")
@Module
abstract class ViewModelModule {
@Binds
    @IntoMap
    @ViewModelKey(MyFragmentViewModel::class)
    abstract fun bindMyViewModel(viewModel: MyFragmentViewModel): ViewModel
    ...
}
2. Always add @Inject in your constructor whether you need dependencies or not
class MyFragmentViewModel @Inject constructor(private val sharePref: SharedPreferences) : ViewModel() {
class MyFragmentViewModel @Inject constructor() : ViewModel() {
3. In your fragment, be sure to instantiate your ViewModel
viewModel = ViewModelProviders.of(this, viewModelFactory).get(MyFragmentViewModel::class.java)

Trivia@Singleton ScopeIf you don’t know, @Singleton is also a scope. It’s provided by the `javax.inject` package. Annotating classes with this scope don’t necessarily mean that it will “magically” make it a Singleton [email protected]ActivityScope and @FragmentScopeYou might think that these scopes are part of dagger-android library but they’re actually not. It’s made by us. It’s just a convention name commonly used by Android community when scoping Activities and Fragments. Some even name it @PerActivity and @[email protected]Binds and @ProvidesThe quickest way to decide when to use either of the two is, if you have to initiliaze the dependency use @Provides, else if you just return the injected parameter use @Binds.

Note: @Binds can only be used in an abstract module class. These two annotations can be used in one module but it’s best if you separate them in their own module classes.

Image Credits: Unsplash

About the author

Arth is an Android developer based in Cebu, Philippines. He focuses on using Kotlin to build Android apps. He writes to help aspiring Android developers to learn more about Android development and give back to the community. When he's not coding, he watches Dota 2 streams. You can learn more about him through his personal website(http://arthlimchiu.com) or blog(http://imakeanapp.com) "Be better today than yesterday"

Leave a Reply

.
Like what you're reading?
Subscribe and get exclusive access to the latest industry news, guides and resources.