loading...
Cover image for Dagger with a Hilt

Dagger with a Hilt

aniketsmk profile image Aniket Kadam ・5 min read

AndroidEntryPoint is now quite a bit better and in general availability so we can now move to this method!

This lets us specify the bare minimum to have a functional Dagger setup, with few drawbacks and get going really quickly with the code we care about.

Looking to rapidly bootstrap a new app with Dagger and Hilt?
Here are some steps to how it might practically be used. You could go in order to jump to the section you'd like.

Basic Setup

  1. Add classpath "com.google.dagger:hilt-android-gradle-plugin:2.28-alpha" to your project level build.gradle (the one at your project root)
  2. Add apply plugin: 'dagger.hilt.android.plugin' to the top of your app level build.gradle
  3. Ensure you have apply plugin: 'kotlin-kapt' in your app level build.gradle as well
  4. Import the following at the app level build.gradle:

implementation "com.google.dagger:hilt-android:2.28-alpha"
kapt"com.google.dagger:hilt-android-compiler:2.28-alpha"

Once you've gotten your setup ready, here's what you do.

Create an Application class

Then annotate it with @HiltAndroidApp

@HiltAndroidApp
class DemoApplication : Application()

Don't forget to add the classname of your application to the Manifest!

    <application
        android:name=".DemoApplication"
        android:allowBackup="true"

Let's get a ViewModel, but first..

Since it's only going to be useful with a repo and some Api passed in:

We're going to be using the standard separation of concerns which means we're going to need three classes

  1. The provider for the Retrofit instance
  2. The API interface for the network call to be made by retrofit
  3. The repository which will take in an instance of the created API and be responsible for exposing it to the ViewModel

We're going to create them in that order only because the app wouldn't compile in an intermediate state otherwise.

Creating a Retrofit Provider

I'll get to what @ApplicationComponent is and why the module is an object along with why this particular provider is marked @Singleton right after showing you code for this module.

import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ApplicationComponent
import io.reactivex.schedulers.Schedulers
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
import javax.inject.Singleton

@InstallIn(ApplicationComponent::class)
@Module
object RetrofitModule {

    @Singleton
    @Provides
    fun getRetrofit(client: OkHttpClient) : Retrofit = Retrofit.Builder().baseUrl("https://something.com")
        .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
        .client(client)
        .build()

    @Provides
    fun getClient() : OkHttpClient = OkHttpClient()
}

Since we only ever want one instance of Retrofit (to improve connection pooling) this is a Singleton provider.
It might feel odd having a default okhttp client provided but this is often modified with interceptors.

This is installed in the Application component since we want the retrofit instance to not only be unique for the entire app, but also at the highest level in the dependency graph so it's available to all other modules.

This follows the standard practise of Modules being Singletons (via the object notation) for faster runtime performance.

Note: Since Dagger 2.25, we no longer need to annotate the functions JvmStatic.

Creating an API interface for the actual call

import io.reactivex.Observable
import retrofit2.http.GET

interface SomethingApi {
    @GET("widgets/")
    fun getWidgets(): Observable<String>
}

The pretend API has an endpoint called widgets and we're going to get ourselves some widgets!

Creating concrete instances of the API

Since it requires retrofit to create concrete instances of the API, we're going to need a module that will manage that.

import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ActivityRetainedComponent
import retrofit2.Retrofit

@InstallIn(ActivityRetainedComponent::class)
@Module
object ApiModule {

    @Provides
    fun getSomethingApi(retrofit: Retrofit): SomethingApi =
        retrofit.create(SomethingApi::class.java)
}

Note that is in an ActivityRetainedComponent since it's intended to be used by a ViewModel that will itself be preserved on activity rotation. The only difference between the ActivityRetainedComponent and the ActivityComponent is that the provided object in the first scope is 'retained' or kept, through configuration changes. See here

Creating a Repository

This repo will abstract away the network and DB api's for the ViewModel


import io.reactivex.Observable
import javax.inject.Inject

class MainRepo @Inject constructor(private val somethingApi: SomethingApi) {
    fun getWidgets() : Observable<String> = somethingApi.getWidgets()
}

Finally the ViewModel

ViewModel Factories no longer need to be created since the Hilt compiler will do that bit of code generation.

You'll need implementation "androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha01" for the @ViewModelInject annotation.

Also kapt "androidx.hilt:hilt-compiler:1.0.0-alpha01" for the hilt compiler that will actually generate the ViewModel factories for you.

import androidx.hilt.lifecycle.ViewModelInject
import androidx.lifecycle.ViewModel

class MainViewModel @ViewModelInject constructor(
    private val mainRepo: MainRepo
) : ViewModel() {

}

Wrapping it up in the Activity

Now we're ready to use this ViewModel in the Activity and it's done with:

import android.os.Bundle
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    private val mainViewModel: MainViewModel by viewModels()

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

To get the viewModels delegate, you'll need to add the activity or fragment kotlin extension libraries depending on where you want to use it.
That's implementation "androidx.activity:activity-ktx:1.2.0-alpha06" or
implementation "androidx.fragment:fragment-ktx:1.3.0-alpha06"

That's it!
You're ready to use dependency injected viewmodels!

If you wanted to use this with fragments and want to share the activity's viewmodel with them, use activityViewModels instead of viewmodels in them.

Check back in later to see how to use this with navigation components and with testing.


Looking to get your devs up to speed on the latest techniques to boost productivity and your time to market? Reach out to me for consulting at consulting@aniketkadam.com

Discussion

pic
Editor guide