Dependency Injection — Dagger hilt in Android

Abhishek Pathak
5 min readJan 3, 2023

--

In this article you are going to learn about the dependency injection and how to deal with it in Android development using Dagger-Hilt

First of all I would like to discuss the problem without dependency injection so what is it?

What is Dependency?

A class which need some other to initialize itself is called dependency or in other terms if a class require to initialize an another class to make itself useful that class is called as dependency in Programming.
Let’s try to understand dependency with an example

In the above diagram I tried to represent a real-time dependency where a car class is dependent on class Engine, so every time car needs to create a instance of engine before using it.

Dependency class

class Engine {
fun start() {
println("Engine started")
}
}

Dependent class 1: Here class Car is initializing by variable initialization

class Car {
private lateinit var engine: Engine

fun start() {
engine = Engine()
engine.start()
println("Car started!!")
}
}
fun main() {
val car = Car()
car.start()
}

Dependent class 2: Here class Car2 is initializing by constructor initialization

class Car2(private val engine: Engine) {
fun start(){
engine.start()
println("Car started")
}
}
fun main() {
val engine = Engine()
val car2 =Car2(engine)
car2.start()
}

Problem statement — If one class is dependent on another class that increases the code tight coupling and duplicities, boilerplate code (unnecessary redundant code).

Let’s go towards solution

Yes, you are in right direction, I am going to discuss about how to deal with dependency injection.

Dependency Injection is a design pattern to decouple the conventional dependency relationship between objects.

Basics of Dagger-Hilt

Dagger Hilt provides a smooth dependency injection way in android.
It reduces various steps in comparison with Dagger2 and providing the better features so definitely we should learn Dagger-hilt to use this advanced cutting edge tool in dependency Injection.

@HiltAndroidApp : Annotation which is required to keep top of the Android Application class
@AndroidEntryPoint : This annotation is required to keep top of the Activity class.
@Inject:This annotation is required to inject field or constructor like repository, services etc.
@HiltViewModel : This is class level annotation that need to keep top of the view model class.
@Module : This is annotation that is require to keep top of the module object class.
@InstallIn : This annotation is require to set up the module with component like SingletonComponent.
@Provides: This annotation require to return feature of each module method.
@Binds: This annotation require to return feature of each module method but here module class must be abstract.

Let’s Understand Dagger-Hilt application by an MVVM implementation

if you want to implement using dagger2 then check this article

Step 1 : Add dependency in your build. gradle

I am using RxJava for asynchronous programming and retrofit for API calls, and Glide for Image loading so below is my all set of dependencies, you can use as per requirement

plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'kotlin-kapt'
id 'dagger.hilt.android.plugin'
}

android {
namespace 'com.learning.hiltdemousingmvvm'
compileSdk 33

defaultConfig {
applicationId "com.learning.hiltdemousingmvvm"
minSdk 21
targetSdk 33
versionCode 1
versionName "1.0"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}

buildFeatures{
viewBinding true
}
}

dependencies {

implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.5.1'
implementation 'com.google.android.material:material:1.7.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.4'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'

//Dagger - Hilt
implementation "com.google.dagger:hilt-android:2.42"
kapt "com.google.dagger:hilt-android-compiler:2.42"

// Retrofit & OkHttp
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.2'

// RxJava 2
implementation "io.reactivex.rxjava2:rxjava:2.2.7"
implementation "io.reactivex.rxjava2:rxandroid:2.1.1"
//RxJava2 with Retrofit
implementation "com.squareup.retrofit2:adapter-rxjava2:2.9.0"

// Glide
implementation 'com.github.bumptech.glide:glide:4.13.2'
annotationProcessor 'com.github.bumptech.glide:compiler:4.13.2'

//lifecycle
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1'
}

Step 2: Add few more dependencies stuff in you build.gradle(app)

// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.42'
}
}

plugins {
id 'com.android.application' version '7.3.1' apply false
id 'com.android.library' version '7.3.1' apply false
id 'org.jetbrains.kotlin.android' version '1.7.20' apply false
}

task clean(type:Delete){
delete rootProject.buildDir
}

Step 3: Creating a module for network call

@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {

@Singleton
@Provides
fun provideBaseUrl(): String {
return BASE_URL
}

@Singleton
@Provides
fun provideLoggingInterceptor(): HttpLoggingInterceptor {
return HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
}

@Singleton
@Provides
fun provideConvertorFactory(): Converter.Factory {
return GsonConverterFactory.create()
}

@Singleton
@Provides
fun provideOkhttpClient(httpLoggingInterceptor: HttpLoggingInterceptor): OkHttpClient {
val okHttpClient = OkHttpClient.Builder()
.addInterceptor(httpLoggingInterceptor)
return okHttpClient.build()
}

@Singleton
@Provides
fun provideRetrofit(
baseUrl: String,
converterFactory: Converter.Factory,
okHttpClient: OkHttpClient
): Retrofit {
val retrofit = Retrofit.Builder()
.baseUrl(baseUrl)
.client(okHttpClient)
.addConverterFactory(converterFactory)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
return retrofit.build()
}

@Singleton
@Provides
fun provideApiService(retrofit: Retrofit): ApiService {
return retrofit.create(ApiService::class.java)
}

@Singleton
@Provides
fun provideRepository(apiService: ApiService): Repository {
return Repository(apiService)
}
}

Step 4: Here is my API service interface for API method

interface ApiService {

@GET(END_POINT)
fun getRandomDog(): Single<ApiResponse>
}

Step 5: Here is my simple app response class

data class ApiResponse(
val message: String,
val status: String
)

Step 6: As Iprefer to keep constant file to add all URL’s

object Constants {
const val BASE_URL = "https://dog.ceo/api/breeds/image/"
const val END_POINT = "random"
}

Step 7: Let’s do constuctor injection at repository

class Repository @Inject constructor(private val apiService: ApiService) {
fun getRemoteData() = apiService.getRandomDog()
}

Step 8: Here is my viewmodel that will deals with all clear separation of logic from UI and injecting the repository using dagger hilt.

Make sure you are using class level annotation @HiltViewModel

@HiltViewModel
class DogViewModel @Inject constructor(private val repository: Repository) : ViewModel() {

val dogResponse = MutableLiveData<ApiResponse>()
val error = MutableLiveData<String>()
private lateinit var disposable: Disposable

fun getRandomDogs() {
disposable = repository.getRemoteData()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{
dogResponse.postValue(it)
}, {
error.postValue(it.toString())
}
)
}

override fun onCleared() {
super.onCleared()
if (this::disposable.isInitialized) {
disposable.dispose()
}
}
}

Step 9: Create a Android app class and add annotation @HiltAndroidApp

@HiltAndroidApp
class App : Application() {
}

Step 10 : Finally let’s deal with UI, here I am adding a activity and injecting
class level annotation called @AndroidEntryPoint

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

private lateinit var binding: ActivityMainBinding
private lateinit var viewModel: DogViewModel

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
setUpViewModel()
initViews()
setUpObserver()
}

private fun setUpObserver() {
with(viewModel) {
dogResponse.observe(this@MainActivity) {
Glide.with(this@MainActivity)
.load(it.message)
.placeholder(R.drawable.ic_launcher_background)
.error(android.R.drawable.ic_dialog_alert)
.into(binding.imageOfDog)
}

viewModel.error.observe(this@MainActivity) {
Toast.makeText(this@MainActivity, it, Toast.LENGTH_SHORT).show()
}
}
}

private fun setUpViewModel() {
viewModel = ViewModelProvider(this)[DogViewModel::class.java]
}

private fun initViews() {
binding.btnNewDog.setOnClickListener {
viewModel.getRandomDogs()
}
}
}

If you wish to look into detail implementation of Dagger-Hilt using MVVM architecture, please check out my GitHub repository.

Thanks for reading this article. Be sure to click 👏 below to applause this article if you found it helpful. It means a lot to me.

--

--