Dependency Injection and Dagger Hilt for Android Apps

What is Dependency Injection?

It is a common programming need that classes reference/ inherit from other classes. Dependency Injection is one of the ways through which a class can take reference of another class.

Why do we need DI?

DI approach helps in maintaining clean architecture, because it allows reusability of the objects. Ease of refactoring and ease of testing is another advantage.

What are the types of DI available for Android?

  1. Constructor Injection : Pass the dependencies of a class to its constructor.

  2. Field(or Setter) injection: Dependencies are instantiated after the class is created. It is helpful to use in android framework classes such as activities and fragments where classes are instatiated by the system.

Examples for manual DI

Code without DI

constructor injection example

Field/Setter Injection example

For big apps, manual approach is not feasible hence libraries like dagger hilt are used.

What is Dagger Hilt?

Dagger is fully static, compile time DI tool for java, kotlin and android. It creates the code which we otherwise would have written manually to work with DIs.

Dagger 2 is the initial framework built for this purpose. Later, Hilt was introduced which is built on top of Dagger2 and it brings more simplicity and ease of use to the entire framework.

How to implement in android?

  1. Add dagger hilt libraries in the gradle file.
//plugins
plugins {
    ...
    id 'kotlin-kapt'
    id 'dagger.hilt.android.plugin'
}

android {
    ...
}

//dependencies

...
dependencies {
    ...
    implementation "com.google.dagger:hilt-android:$hilt_version"
    kapt "com.google.dagger:hilt-android-compiler:$hilt_version"
}

Dagger generates code at compile time using annotation processor. Annotation processors are supported by kapt plugin. Hence we also need to add kapt plugin (Kotlin Annotation Processing tool) to gradle file.

  1. All the apps using Dagger Hilt should contain an application class and it should be annotated with @HiltAndroidApp. This creates an application level base dependency container, which provides dependencies to other components of the app.
@HiltAndroidApp
class SchoolApplication: Application()
  1. Once application level component is set-up then Hilt provides dependencies to other classes which have @AndroidEntryPoint annotation.
@AndroidEntryPoint
class MainActivity : AppCompatActivity(){
.....
@Inject lateinit var studentList: StudentsListAdapter
}
@AndroidEntryPoint
class StudentsFragment:Fragment(){

...
}
@HiltViewModel
class StudentsViewModel @Inject constructor(private val repository: SchoolRepository) :
    ViewModel() {
.....
}

Note : If you annotate an android class with @AndroidEntryPoint annotation, then the classes depending on it must also be annotated. In the above case, fragments are annotated with @AndroidEntryPoint annotation, so does the MainActivity class.

ViewModels must be annotated with @HiltViewModel annotation.

To get dependency from any component, use @Inject annotation. In the above example - ViewModel class is obtaining dependency from repository component by using constructor injection. And main activity is using field injection to get dependency from adapter component.

  1. While using external libraries like Retrofit, Room database, etc. you can create a module class with @Module annotation and then create a function inside Hilt module with @Provide annotation. This will tell the Hilt to provide instances of the classes which you don't own. Hilt modules should also have @InstallIn annotation to indicate in which android class the modules will be used.

     @Module
     @InstallIn(SingletonComponent::class)
     object SchoolModule {
    
         @Provides
         @Singleton
         fun provideRetrofit(): Retrofit =
             Retrofit.Builder()
                 .....
    
         @Provides
         @Singleton
         fun provideDatabase(app: Application):SchoolDatabase =
             Room.databaseBuilder(app,SchoolDatabase::class.java,"school_database")
                 .fallbackToDestructiveMigration()
                 .build()
     }
    

Conclusion

Dependency Injection is widely used concept in app development and it is required to maintain good app architecture. To learn this concept in detail with an example follow this code lab provided by google.