Skip to content

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.

This is further reflected in a developer’s skill level. A low mid or even a junior android developer usually won’t have any problems adding to a dagger dependency tree that’s already setup, but will encounter many problems if they tried to make their own dagger dependency tree all the way from its roots.

And there are various blog posts online that go through creating a dagger setup, although they can be helpful, most of them do still have problems that lead to an incomplete solution.

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 Structure

Pay your attention to the di and mvvm packages, as the rest are irrelevant to our setup. It’s a rather simple structure. You can setup the mvvm package however you want it as it doesn’t really matter. This setup does however rely a lot on using modules to segregate dependencies without much scoping at all, so I keep them all in a package. But at the end of the day, set it up however you want to as this makes no difference to the functionality of the setup. That’s all in the code.

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.

To reduce boilerplate with multiple fragments and viewmodels, create a BaseFragment class. This isn’t strictly needed, but I find it to be a nice layer of abstraction, and will set us up nicely for any common behaviour we want our fragments to follow.

abstract class BaseFragment<V: ViewModel>: DaggerFragment() {
    
    @Inject
    protected lateinit var viewModel: V
}

This is where the first bit of Dagger comes into play. We have to use the @Injectannotation when creating a viewModel, and not as a constructor parameter for the fragment because we don’t want the viewModel’s lifetime to be tied to the fragments, or we lose all the benefit of having an MVVM architecture.

class HomeFragment : BaseFragment<HomeViewModel>()

class HomeViewModel @Inject constructor() : ViewModel()

The next thing to do is create the concrete fragment and viewModel. At least one of each for now. With HomeFragment extending BaseFragment and passing in the generic type parameter of HomeViewModel, it will automatically injected once we finish the setup.

Setting up the DI

abstract class ViewModelFactory : ViewModelProvider.Factory {

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

    protected abstract fun create(): ViewModel
}

Directly under the di package, create the ViewModelFactory class. We’ll need this later.

@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
    ]
)
class AppModule

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

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

Go back to AppComponent and add AppModule to it.

@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
class HomeModule {

    @Provides
    internal fun provideViewModel(
        someString: String
    ): HomeViewModel = object: ViewModelFactory() {
        override fun create(): ViewModel = HomeViewModel(
            someString
        )
    }.create(HomeViewModel::class.java)
  
    @Provides
    internal fun provideSomeString(): String = "Hello World"
}

Finally, create the HomeModule which is the module tied directly to our HomeFragment, which makes use of the ViewModelFactory class to create our ViewModel and provide it to our fragment which it receives via the @Inject member variable in BaseFragment.

I’ve seen other articles use @Binds to provide the viewModel, along with a ViewModelKey, making use of Dagger Multibindings. The problem I encountered with this approach is that it’s rather difficult when you need any constructor parameters in the viewModel which is pretty much any app ever.

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.

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 ༼ つ ◕_◕ ༽つ

 

Published inAndroidDaggerMVVM