Android Dagger Setup with MVVM (HAT 1)

As apps grow larger in scale, separation of classes by concern becomes increasingly important to keep an app maintainable and as this happens, classes will need to depend on each other.

It quickly comes to a point when it’s no longer practical to instantiate objects within the classes they are used, as you will also need to instantiate each and every single one of their dependencies.

The solution to this? Have another service instantiate these dependencies within itself, and inject to each class where they are needed. This is called Dependency Injection.

This is the first episode of Holiday Android Training. Roll the intro!

Dagger and Koin are the two most renown dependency injection solutions for Android, with dagger being the more popular of the two, mainly because it’s a bit more robust.

But the ever present problem with Dagger is that there’s no definitive way on how to use it, proven by the fact that we have so many different versions of dagger which were a result of Google trying to figure out a single definitive way to use it (but still not ultimately coming up with an answer). Furthermore, the multiple versions of dagger only adds to the confusion.

So different apps will have their own dagger setups unique to them. And while Dagger can be a bit of pain to setup when initially creating the app, once it’s setup properly, it’s fairly easy to use.

So this will be my attempt to once and for all, create the perfect dagger setup…. at least until next year when this becomes outdated but you know what let’s goo

Dagger Setup

So this setup is for an app in the MVVM architecture with a single activity that has multiple fragments but is designed in a way that it can scale just as easily if I want to add more activities. This is also modeled quite closely to the setup from the post written by Damilola Omoyiwola but with some of my own touches to make it more scaleable and fix some of the problems I found with it.

One thing I also want to get out of the way is that this setup makes use of Dagger Android which is now depracated for the newer Dagger Hilt. Hilt is still in alpha though, and although it looks solid, production use of it is still a controversial topic among many developers. I’m still yet to try it out myself but that will have to wait another time.

We start with the gradle setup. We only need to add the dagger dependencies to make this work.

In your app/build.gradle file, ensure first that you have kapt enabled

plugins {
    ...
    id 'kotlin-kapt'
}

Then add the Dagger under dependencies

dependencies {
    // Dagger
    implementation "com.google.dagger:dagger-android:$daggerVersion"
    implementation "com.google.dagger:dagger-android-support:$daggerVersion"
    kapt "com.google.dagger:dagger-compiler:$daggerVersion"
    kapt "com.google.dagger:dagger-android-processor:$daggerVersion"
}

daggerVersion = 2.30.1

I’m currently using dagger version 2.30.1

Project StructureThere is a lot of flexibility in how you structure your project, but in case you need some reference, here’s mine. I’ll be saying what folders and packages classes should be created in, but this is purely just to serve as a guide.

Setting up the MVVM

Before we get injecting, let’s set up the components that depend on each other, those being our activities, fragments, and viewmodels.

class MainActivity : DaggerAppCompatActivity() {

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
  }
}

Since I’m using fragments in this app, MainActivity here requires no change. However, if you want to make an app that handles the view in the activity rather than the fragment, simply apply the later setup for the fragment into the activity.

<!-- activity_main.xml -->
<androidx.fragment.app.FragmentContainerView
    android:id="@+id/fragment_container_view"
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:name="androidx.navigation.fragment.NavHostFragment"
    app:defaultNavHost="true"
    app:navGraph="@navigation/nav_graph"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintBottom_toBottomOf="parent" />

Modify activity_main.xmlto use a FragmentContainerView for hosting the fragments. This is why we don’t need any extra Kotlin code in MainActivity.

<!-- nav_graph.xml -->
<?xml version="1.0" encoding="utf-8"?>
<navigation
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/nav_graph"
    app:startDestination="@id/homeFragment">

    <fragment
        android:id="@+id/homeFragment"
        android:name="com.ericdecanini.shopshopshoppinglist.mvvm.fragment.home.HomeFragment"
        android:label="HomeFragment" >
    </fragment>

</navigation>

In the res/xml folder, create a file called nav_graph where home_fragment is automatically loaded into the view as the start destination. I’ll go deeper into fragments and navigation in another episode of HAT.

class HomeViewModel @Inject constructor() : ViewModel()

Next, create your ViewModel class. It must extend ViewModel, provided by AndroidX, and you must also use the @Inject annotation for your constructor. This is how you will be injecting any parameters your view model will need.

class HomeFragment : DaggerFragment() {

    @Inject
    lateinit var viewModelFactory: ViewModelProvider.Factory
    private val viewModel by lazy {
        ViewModelProvider(this, viewModelFactory).get(HomeViewModel::class.java)
    }
  
}

Finally, create your fragment. Make sure you extend Dagger Fragment, then use the @Inject annotation to inject the viewModelFactory into the fragment, and then by lazy, use ViewModelProvider to get your HomeViewModel class. This way, the ViewModelProvider can get the same instance of the view model, even after the fragment itself gets destroyed and recreated after configuration changes, like screen rotation.

Setting up the DI

@Suppress("UNCHECKED_CAST")
class ViewModelFactory @Inject constructor(private val viewModels: MutableMap<Class<out ViewModel>, Provider<ViewModel>>) :
    ViewModelProvider.Factory {

    override fun <T : ViewModel> create(modelClass: Class<T>): T =
        viewModels[modelClass]?.get() as T
}

Directly under the di package, create the ViewModelFactory class. This will be responsible for creating your view models.

@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
@MapKey
internal annotation class ViewModelKey(val value: KClass<out ViewModel>)

Next is to create the ViewModelKey, also directly under di. This annotation class will allow us to make use of dagger multibindings to bind and provide our different view models.

@Singleton
@Component(
    modules = []
)
interface AppComponent : AndroidInjector<ShopshopApplication> {

    @Component.Factory
    interface Factory {
        fun create(@BindsInstance application: Application): AppComponent
    }
}

Proceed by creating the AppComponent interface directly under the di package.

class ShopshopApplication: DaggerApplication() {

    override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
        return DaggerAppComponent.factory().create(this)
    }
}

Build your app to let kapt generate the DaggerAppComponent, then directly under your app package, create an application class that extends DaggerApplication.

<application
    android:name=".ShopshopApplication"
    ...

Don’t forget to declare your newly created application class in the manifest.

@Module(
    includes = [
        AndroidSupportInjectionModule::class,
        ActivityBuildersModule::class,
        ViewModelFactoryModule::class
    ]
)
class AppModule

Next, create AppModule under the module package. AndroidSupportInjectionModule should be available from the android support dependency added in gradle, but the other two will be unresolved at this point. Later on, you can provide any dependencies required across the whole of the app in AppModule.

@Singleton
@Component(
    modules = [AppModule::class]
)
interface AppComponent

Go back to AppComponent and add AppModule to it.

@Module
abstract class ViewModelFactoryModule {

    @Binds
    internal abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory

}

Next, still under the module package, create ViewModelFactoryModule. This needs to be an abstract class so we can use the @Binds annotation to provide the factory.

@Module
abstract class ActivityBuildersModule {

    @ContributesAndroidInjector(
        modules = [MainModule::class]
    )
    abstract fun contributeMainActivity(): MainActivity
}

Next, create ActivityBuildersModule under di/module/activity. This is where you can “contribute” any activities and assign their own modules to them.

@Module(includes = [MainFragmentsModule::class])
class MainModule
@Module
abstract class MainFragmentsModule {

    @ContributesAndroidInjector(modules = [HomeModule::class])
    abstract fun contributeHomeFragment(): HomeFragment

}

Still under di/module/activity, create MainModule and MainFragmentsModule. MainModule is a module tied only to MainActivity, so there you can provide any dependencies required within the activity and all its fragments.

Then MainFragmentsModule is where you can contribute any fragments that will be used within MainActivity and assign their own modules to them. This is kept separate from MainModule purely because personally, I find it cleaner to keep the fragment-related dependencies and the other activity dependencies separated from each other.

@Module(includes = [HomeModule.ViewModelModule::class])
class HomeModule {

    @Provides
    internal fun provideSomeString(): String = "Hello World"

    @Module
    internal abstract class ViewModelModule {
        @Binds
        @IntoMap
        @ViewModelKey(HomeViewModel::class)
        internal abstract fun bindHomeViewModel(viewModel: HomeViewModel): ViewModel
    }
}

Finally, create the module for your fragment under di/module/fragment, mine being HomeModule. This is where you provide any dependencies your fragment or view model will need. To then provide the ViewModel, create an abstract class within HomeModule called ViewModelModule where you can use Dagger Multibindings to bind your ViewModel using the ViewModelKey we created earlier.

I left someString above just to show an alternate way to provide the viewModel with constructor parameters. Of course later on, replace this with any parameters you actually will need, but for now, you can use someString to test that the setup works.

class HomeViewModel @Inject constructor(someString: String)

Because we used the @Inject constuctor annotation in our ViewModel, any dependencies provided at the fragment, activity, or app level will be automatically injected into our ViewModel.

private val viewModel by lazy {
    ViewModelProvider(this, viewModelFactory).get(HomeViewModel::class.java)
}

And because we provided our ViewModel using ViewModelProvider in our fragment, our ViewModel will be able to survive configuration changes.

Conclusion (& source code)

And that’s it really. I believe this to be scalable and flexible to setup required by most apps where there is a clear place to add more activities (ActivityBuildersModule), more fragments (i.e. MainFragmentsModule), and no special scopes were required either.

Of course, if you need to add other services, you can follow standard practices such as creating a NetworkModule class to provide all your Retrofit dependencies, then add that to AppModule.

The source code can be found here on Github. That’s a project that’s so much more than just this dagger setup so you will find the classes will have more in them than the snippets in this tutorial but the structure is the same.

I hoped this post helped and as always, happy coding ༼ つ ◕_◕ ༽つ

Subscribe to the Newsletter