Using Dagger Hilt

Wamae Benson
3 min readNov 28, 2020

--

A brief history of Dagger.

XML injection was created first followed by Dagger 1, Dagger then Dagger Hilt for Android.

Dagger 2 was verbose you had to add too much boilerplate code. This made it hard for developers to ramp up quickly and get to the main issue which is solving the problem i.e. business logic.

Dagger 2 for Android was way more complex than it needed to be. Building a dependency graph was complicated — enter Dagger Hilt.

A hilt is a handle for a dagger/sword — Dagger Hilt makes it simple to implement dependency injection.

Here are some of the familiar annotations used in Android — the annotations are shortcuts that have boilerplate code in the back — Check the generated files in your own Android studio; imagine having to write all of that.

Here is an example of an app using Dagger Hilt.

Modules

AppModule.kt

...imports@Module
@InstallIn(ApplicationComponent::class)
class AppModule {
@Singleton
@Provides
fun provideAuthRepository (api: ERPAPIService): IAuthRepository {
return AuthRepository(api) as IAuthRepository
}
@Singleton
@Provides
fun provideSharedPreferences(application: Application): SharedPreferences {
return PreferenceHelper.defaultPrefs(application as Context)
}
@Provides
fun providesInAppUpdateManager(application: Application): AppUpdateManager {
return AppUpdateManagerFactory.create(application)
}
@Provides
fun providesPlayServiceExecutor(): Executor {
return TaskExecutors.MAIN_THREAD
}
}

RetrofitModule.kt

...imports@Module
@InstallIn(ApplicationComponent::class)
class ReftrofitModule{
@Singleton
@Provides
fun provideGson(): Gson {
return Gson()
}
@Singleton
@Provides
fun provideERPAPIService(sharedPreferences: SharedPreferences, gson: Gson): ERPAPIService {
val httpClient = getERPHttpClient(sharedPreferences)return Retrofit.Builder()
.baseUrl(BuildConfig.BASE_URL_ERP)
.client(httpClient.build())
.addConverterFactory(GsonConverterFactory.create(gson))
.build().create(ERPAPIService::class.java)
}
@Singleton
@Provides
fun provideHttpHeaderInterceptor(preferences: SharedPreferences): ERPHttpHeaderInterceptor {
return ERPHttpHeaderInterceptor(preferences)
}
@Singleton
@Provides
fun provideCoroutineContext(): ContextProviders {
return ContextProviders.getInstance()
}
private fun getERPHttpClient(sharedPreferences: SharedPreferences): OkHttpClient.Builder {val headersInterceptor = ERPHttpHeaderInterceptor(sharedPreferences)
val loggingInterceptor = getHttpLoggingInterceptor()
val httpClient = OkHttpClient.Builder()
.readTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.connectTimeout(10, TimeUnit.SECONDS)
httpClient.addInterceptor(headersInterceptor)
if (BuildConfig.DEBUG) httpClient.addInterceptor(loggingInterceptor)return httpClient
}
private fun getHttpLoggingInterceptor(): HttpLoggingInterceptor {
val logging = HttpLoggingInterceptor()
logging.level = HttpLoggingInterceptor.Level.BODY
return logging
}
}

Repository

AuthRepository.kt

...importsclass AuthRepository @Inject constructor(private val apiService: ERPAPIService) : IAuthRepository {
override suspend fun login(credentials: Credentials): Resource<ERPBaseResponseItem<LoginResponse>> {
return try {
val response = apiService.login(credentials)
if(response.isSuccessful) {
response.body()?.let {
return@let Resource.success(it)
} ?: Resource.error(ERPBaseResponseItem(LoginResponse("USER_ID"), false,null),"ERROR")
} else {
Resource.error(ERPBaseResponseItem(LoginResponse("An unknown error occurred"), false,null),"ERROR")
}
} catch(e: Exception) {
Resource.error(ERPBaseResponseItem(LoginResponse("Couldn't reach the server. Check your internet connection"), false,null),"ERROR")
}
}
override suspend fun getAccessToken(appCredentials: AppCredentials): Resource<AccessTokenBaseResponse> {
return try {
val response = apiService.getAccessToken(appCredentials)
if(response.isSuccessful) {
response.body()?.let {
return@let Resource.success(it)
} ?: Resource.error(AccessTokenBaseResponse(null, null, success = false),"ERROR")
} else {
Resource.error(AccessTokenBaseResponse(null, "An unknown error occurred", success = false),"ERROR")
}
} catch(e: Exception) {
Resource.error(AccessTokenBaseResponse(null, "Couldn't reach the server. Check your internet connection", success = false),"ERROR")
}
}
}

IAuthRepository.kt

This interface makes testing easy, we could swap out the repository with FakeRepository when ariting automation tests.

...importsinterface IAuthRepository {suspend fun login(credentials: Credentials) : Resource<ERPBaseResponseItem<LoginResponse>>suspend fun getAccessToken(credentials: AppCredentials) : Resource<AccessTokenBaseResponse>
}

Application

Application.kt

...imports@HiltAndroidApp
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) {
Timber.plant(Timber.DebugTree())
} else {
Timber.plant(ReleaseTree())
}
}
}

View Models

...importsclass AuthViewModel @ViewModelInject constructor(
private val repository: IAuthRepository
) : BaseViewModel() {
fun login(credentials: Credentials) = liveData(Dispatchers.IO) {
emit(Resource.loading(data = null))
try {
emit(repository.login(credentials))
} catch (exception: Exception) {
emit(handleExceptions(exception))
}
}
fun getAccessToken(appCredentials: AppCredentials) = liveData(Dispatchers.IO) {
emit(Resource.loading(data = null))
try {
emit(repository.getAccessToken(appCredentials))
} catch (exception: Exception) {
emit(Resource.error(data = null, message = exception.message ?: "Error Occurred!"))
}
}
}

Fragment

...import@AndroidEntryPoint
open class BaseFragment : Fragment() {
...more code
}
..importsclass CalibrationFragment : BaseFragment(){ ...more code
}

Activity

...import@AndroidEntryPoint
class LoginActivity : BaseActivity() {
private var rememberMe: Boolean = false
private lateinit var email: String
private val viewModel: AuthViewModel by viewModels()

...more code
}

Why Dagger Hilt?

Reduced boilerplate as compared to Dagger 2 allows you to focus on the business logic of your app — what really matters. Simplified configuration, the annotations it has are enough to model complex dependencies 😎. In Dagger 2 one hard to make their own annotations. 😡 and testing is way easier.

References:

https://ebinjoy999.medium.com/introduction-with-some-history-dagger-2-part2-f37086745911

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

https://dagger.dev/hilt/

--

--