Inject ViewModel using Hilt

Inject ViewModel Using Hilt

Saurabh Vashisht
The Startup

--

Dependency Injection is the first step you have to take towards building an app that can be tested. So today we will use Hilt to introduce view model injection into a Fragment.
If you wish to carry along with this article you can checkout the starting code here

Setting up Hilt

First of all we need to add dependencies.

Add the hilt-android-gradle-plugin plugin to your project's root build.gradle file:

buildscript {
...
dependencies {
...
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.38.1'
}
}

Then, apply the Gradle plugin and add these dependencies in your app/build.gradle file:

...
apply plugin: 'kotlin-kapt'
apply plugin: 'dagger.hilt.android.plugin'



dependencies {
kapt "com.google.dagger:hilt-android-compiler:2.37"
implementation "com.google.dagger:hilt-android:2.37"
kapt "androidx.hilt:hilt-compiler:1.0.0-alpha03"
implementation "androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha03"
}

Hilt uses Java 8 features. To enable Java 8 in your project, add the following to the app/build.gradle file:

android {
...
compileOptions {
sourceCompatibility = 1.8
targetCompatibility = 1.8
}
kotlinOptions {
jvmTarget = "1.8"
}
}

If you’re following along, now’s the time to sync your gradle files.

Hilt Application class

All apps that use Hilt must contain an Application class that is annotated with @HiltAndroidApp.

@HiltAndroidApp
class ExampleApplication : Application() { ... }

This triggers Hilt’s code generation, including a base class for your application that serves as the application-level dependency container.

This part is taken as is from official documentation. You can check it out here.

https://developer.android.com/training/dependency-injection/hilt-android

This concludes the setting up part. If you do not wish to go through the setup steps, you can check out code for a sample application with basic setup complete here

In this app I am trying to build a user interface to browse any RSS feed.

When we open the app, we see MyChannelsFragment.kt. To show data in this fragment we have a ViewModel MyChannelsViewModel. This view model will get it’s data from a repository MyChannelRepository.

It is the responsibility of repository to get data from the local storage or from a remote server.

In this tutorial, we will inject MyChannelRepository into MyChannelsViewModel. And then MyChannelsViewModel will be injected into MyChannelsFragment.

Let’s get started.

Hilt can provide dependencies to classes that have @AndroidEntryPoint annotation. so we add this to our fragment.

@AndroidEntryPoint
class MyChannelsFragment : Fragment() {
.....
}

Since fragment is used in HomeActivity, HomeActivity would also have to be annotated with @AndroidEntryPoint.

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
...
}

Now we need to tell Hilt that we wish to inject our view model. Since our viewmodel will be dependent upon implementation of MyChannelRepository i.e. MyChannelRepositoryImpl we will have to tell Hilt how to inject this transitive dependency.

Currently MyChannelsViewModel class looks like this

class MyChannelsViewModel (private val myChannelRepository: MyChannelRepository) : ViewModel() {

val suggestedChannels = myChannelRepository.getSuggestedChannels()

}

Now add @HiltViewModel annotation to view model to mark it’s constructor for injection. MyChannelsViewModel’s constructor should have @Inject with its constructor too. MyChannelsViewModel class will now look like this

@HiltViewModel
class MyChannelsViewModel @Inject constructor(private val myChannelRepository: MyChannelRepository) : ViewModel() {

val suggestedChannels = myChannelRepository.getSuggestedChannels()

}

Now all that’s left is to tell Hilt how MyChannelRepository instance will be provided to MyChannelsViewModel.

To accomplish this, we need to create a module class that will tell hilt how to provide an implementation of MyChannelRepository.

Since MyChannelRepository is an interface it’s constructor cannot be called directly. Having interfaces exposed to view model allows us to expose only behavior/data to the view model which does not need to know how the repository will fetch data.

Create a new package di and in this package create a new class called RepositoryModule. This class will look like this

package com.rssreader.di

import com.rssreader.ui.channels.ChannelRepositoryImpl
import com.rssreader.ui.channels.MyChannelRepository
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ActivityComponent


//Repositories will live same as the activity that requires them
@InstallIn(SingletonComponent::class)
abstract class RepositoryModule {

@Binds
abstract fun providesChannelRepository(impl: ChannelRepositoryImpl): MyChannelRepository

}

Lets examine each part of this class in detail :

@Module

This annotation tells hilt that this is a class that contributes to providing objects for injection.

@InstallIn(SingletonComponent::class)

In simple terms this tells Hilt to initialize the objects as singleton. i.e. only one instance of MyChannelRepository will be created.

@Binds
abstract fun providesChannelRepository(impl: ChannelRepositoryImpl): MyChannelRepository

To tell Hilt what implementation to use for an interface, you can use the @Binds annotation on a function inside a Hilt module.

The function providesChannelRepository (in simple terms) tells Hilt to use an instance of ChannelRepositoryImpl whenever an injection operation requires an object of type MyChannelRepository.

Before this arrangement works, we will have to tell Hilt how to produce an instance of ChannelRepositoryImpl. To acheive this add @Inject constructor to ChannelRepositoryImpl.

The class will now look like this

class ChannelRepositoryImpl @Inject constructor() : MyChannelRepository {
...
}

Now hilt can instantiate our view model when it has to be injected. Now we just need to add our view model to the MyChannelsFragment. We can do that by

val viewModel by viewModels<MyChannelsViewModel>()

And it’s done. Now Hilt can inject MyChannelsViewModel into MyChannelsFragment.

To get a better handle of Hilt and its anootations checkout this awesome codelab

If you wish to check out the final code for this article, you can find the finished code here.

--

--