Dagger with a Hilt
Aniket Kadam
Posted on July 24, 2020
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.
- Setup
- Create an Application class
- Let's get a ViewModel, but first..
- Creating a Retrofit Provider
- Creating an API interface for the actual call
- Creating concrete instances of the API
- Creating a Repository
- Finally the ViewModel
- Wrapping it up in the Activity
Basic Setup
- Add
classpath "com.google.dagger:hilt-android-gradle-plugin:2.28-alpha"
to your project level build.gradle (the one at your project root) - Add
apply plugin: 'dagger.hilt.android.plugin'
to the top of your app level build.gradle - Ensure you have
apply plugin: 'kotlin-kapt'
in your app level build.gradle as well - 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
- The provider for the Retrofit instance
- The API interface for the network call to be made by retrofit
- 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
Posted on July 24, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.