In this blog we are going to see about dependency injection and how we can integrate hilt for dependency injection in android application development.
What is dependency
When class A requires some of the functionalities from other classes ( class B ) to perform a task , then class A is said to be dependent on class B.
Class A {
Class B = new B()
B.perform()
}
In general, we will create an instance of class B inside class A. What if class A is dependent on more than one class. Then we need to create instances of each class to make use of it.
This will make it tedious to refactor or test the class.
This is where dependency injection comes in to solve the problem. The class A doesn’t need to create the required class instances. Instead we can inject the required classes via constructor. This will keep the class A cleaner and testable.
Void main(){
B b = new B()
A a = new A(b)
}
Class A (B b){
b.perform()
}
This method of injecting is known as manual dependency injection.
We can use this similar way in android to inject the dependencies to the required classes.
Dependency in android architecture
In the above diagram you can see that each module depends on modules in the below level.
Repositories require remote service or local database service to get data.
Viewmodel requires the repository to get the data and present it to the view.
Dependencies can be injected by either manually or using libraries that create the dependencies on compile time.
To know about the viewModels and repositories in detail check out the MVVM architecture pattern blog.
Manual dependency injection has its own pros and cons.
Pro’s
- It makes the classes loosely coupled.
- Increase reusability.
- Makes the classes testable.
Con’s
- To initialize a class we still need to create instances of the dependencies manually
- Creating a singleton instance of the classes is harder.
- It will create a lots of boilerplate code.
To overcome this there are few frameworks which take care of creating and maintaining the dependency.
- Dagger is a compile time dependency injection framework for both java and android. It will create and provide the required dependency.
- Hilt is built on top of dagger and it makes dependency injection easy with android components.
In this blog we are going to see how we can set up and integrate Hilt in our application. To know more about dependency injection in android check out the documentation – https://developer.android.com/training/dependency-injection
Hilt Integration
Add the following dependencies to your project.
In project level build.gradle
buildscript {
...
dependencies {
...
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.28-alpha'
}
}
App level build.gradle
...
apply plugin: 'kotlin-kapt'
apply plugin: 'dagger.hilt.android.plugin'
android {
...
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation "com.google.dagger:hilt-android:$hilt_version"
kapt "com.google.dagger:hilt-compiler:$hilt_version"
}
Click sync.
All application that uses Hilt must have an application class annotated with @HiltAndroidApp
@HiltAndroidApp
class AndroidPatternsApplication : Application()
This will create a dependency container attached to the application lifecycle. It will provide dependencies to the required class.
Next we need to set each activity/fragment that uses hilt dependency injection with @AndroidEntryPoint. This will create containers for each class and provide the required dependencies.
@AndroidEntryPoint
class LoginActivity : AppCompatActivity() { }
Now we have created a hilt application and defined containers for class. Now we need to notify what needs to be injected. One we can do is via constructor injection. For that we need to annotate with @Inject.
@HiltViewModel
class LoginActivityViewModel @Inject constructor(private val loginRepository: LoginRepositoryInterface) :
ViewModel() { }
Hilt provides @HiltViewModel annotation to enable injection of the dependencies. Any activitiy / fragments annotated with @AndroidEntryPoint
can get the ViewModel
instance as normal using ViewModelProvider
or the by viewModels()
KTX extension:
Hilt Modules
A Hilt module is a class that is annotated with @Module. Like a Dagger module, it informs Hilt how to provide instances of certain types. Unlike Dagger modules, you must annotate Hilt modules with @InstallIn to tell Hilt which Android class each module will be used or installed in.
@Module
@InstallIn(SingletonComponent::class)
object AppModule { }
In some cases the hilt doesn’t know how to provide instances of those classes like interfaces and external libraries ( Retrofit , Room ). We can define how the instances can be provided to the hilt using @Provide annotation.
@Module
@InstallIn(SingletonComponent::class)
object AppModule {
@Singleton
@Provides
fun loginRepository(): LoginRepositoryInterface {
return LoginRepository()
}
}
The annotated function supplies the following information to Hilt:
- The function return type tells Hilt what type of instances the function provides.
- The function parameters tell Hilt the dependencies of the corresponding type.
- The function body tells Hilt how to provide an instance of the corresponding type. Hilt executes the function body every time it needs to provide an instance of that type.
@Singleton annotation notifies the scope of the instance, it will make sure that this instance is created once for that module.
To know more about modules and scope check out the documentation – https://developer.android.com/training/dependency-injection/hilt-android
To see the real time implementation of the dependency injection with hilt, checkout the features blogs Login and Product.
Since we use hilt for dependency injection we also need to enable hilt for dependency injection while testing.
As we know we can create multiple modules to provide dependencies. We can also create a test module for providing dependencies for testing like this.
@TestInstallIn(components = [SingletonComponent::class], replaces = [AppModule::class])
@Module
object TestModule {
@Singleton
@Provides
fun loginRepository(): LoginRepositoryInterface {
return FakeLoginRepository()
}
@Singleton
@Provides
fun productRepository(): ProductRepositoryInterface {
return FakeProductRepository()
}
}
The annotation denotes that this hilt module will be replaced for AppModule while testing. We can use this module to return fake repositories for testing.
To know about testing check out the blog on Testing and to see the real time implementation of testing with hilt checkout the feature blogs Login testing and Product testing
I hope this blog gave you an idea about dependency injection and how we can inject dependencies in android using hilt. For detailed usage of hilt in application check out the feature modules of the demo application. Android best practice
Happy learning
Team Appmetry