Jetpack Compose App - Hilt with KSP

Objective:

It is time to set up the app with dependency injection. So far, all dependencies were manually constructed and injected. We will use the official Google's dependency injection framework - Hilt. Hilt traditionally worked with KAPT (Kotlin Annotation Processing Tool), but it worked with Java annotations and was/is notoriously slow. We now have a better alternative - KSP (Kotlin Symbol Processing), which is advertised as a "a Kotlin-first alternative to kapt".

Dependencies

Add the following to the toml catalog:


[versions]
#other entries here, omitted for brevity
ksp = "2.0.0-1.0.21"
hilt = "2.51.1"
hiltNavigation = "1.2.0"
hiltExtensions = "1.2.0"

[libraries] 
#other entries here, omitted for brevity
dagger-hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hilt" }
dagger-hilt-compiler = { module = "com.google.dagger:hilt-android-compiler", version.ref = "hilt" }
hilt-compiler = { module = "androidx.hilt:hilt-compiler", version.ref = "hiltExtensions" }
hilt-navigation-compose = { module="androidx.hilt:hilt-navigation-compose", version.ref ="hiltNavigation"}

[plugins]
#other entries here, omitted for brevity
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }

Add ksp and hilt plugins to the app module build.gradle.kts so that it looks like below:


plugins {
    alias(libs.plugins.androidApplication)
    alias(libs.plugins.jetbrainsKotlinAndroid)
    alias(libs.plugins.compose.compiler)
    alias(libs.plugins.ksp)
    alias(libs.plugins.hilt)
}

Add ksp and hilt dependencies to the dependencies block of the app module build.gradle.kts so that it looks like below:


dependencies {

    implementation(libs.androidx.activity.compose)
    implementation(libs.androidx.compose.material3)
    implementation(libs.androidx.core.ktx)
    implementation(libs.androidx.core.splash.screen)
    implementation(libs.androidx.lifecycle.runtime.ktx)
    implementation(libs.androidx.compose.material)
    implementation(libs.androidx.lifecycle.runtime.compose)
    implementation(platform(libs.androidx.compose.bom))
    implementation(libs.androidx.ui)
    implementation(libs.androidx.ui.graphics)
    implementation(libs.androidx.ui.tooling.preview)
    implementation(libs.androidx.navigation.common.ktx)
    implementation(libs.androidx.navigation.runtime.ktx)
    implementation(libs.androidx.navigation.compose)
    implementation(libs.androidx.datastore.preferences.core.jvm)
    implementation(libs.androidx.datastore.preferences)
    implementation(libs.coil.kt.compose)
    implementation(libs.okhttp3)
    implementation(libs.retrofit2)
    implementation(libs.retrofit2.gson.converter)
    implementation(libs.androidx.appcompat)
    implementation(libs.androidx.palette)
    implementation(libs.androidx.compose.materialWindow)
    implementation(libs.kotlinx.datetime)
    //ksp/hilt
    ksp(libs.dagger.hilt.compiler)
    ksp(libs.hilt.compiler)
    implementation(libs.dagger.hilt.android)
    implementation(libs.hilt.navigation.compose)
    
    implementation(project(":common"))
    implementation(project(":data"))
    implementation(project(":api"))
    implementation(project(":models"))
    implementation(project(":usecase"))
    
    testImplementation(libs.junit)
    androidTestImplementation(libs.androidx.junit)
    androidTestImplementation(libs.androidx.espresso.core)
    androidTestImplementation(platform(libs.androidx.compose.bom))
    androidTestImplementation(libs.androidx.ui.test.junit4)
    androidTestImplementation(libs.androidx.navigation.testing)
    androidTestImplementation(libs.androidx.compose.ui.test)
    debugImplementation(libs.androidx.ui.tooling)
    debugImplementation(libs.androidx.ui.test.manifest)
}

Components built

Add or modify the following components:

  • ThisDayInHistoryApp.kt: Add - Hiltified app
  • MainActivity.kt: Modify - add Hilt annotation to indicate that this is now a Hilt-injected activity
  • HistoryViewModel.kt: Modify - add Hilt annotations to indicate that this is now a Hilt-injected activity

Code

ThisDayInHistoryApp.kt

Add ThisDayInHistoryApp.kt to the com.coroutines.thisdayinhistory package in the app module:


package com.coroutines.thisdayinhistory

import android.app.Application
import coil.ImageLoader
import coil.ImageLoaderFactory
import coil.disk.DiskCache
import coil.memory.MemoryCache
import coil.util.DebugLogger
import dagger.hilt.android.HiltAndroidApp

@HiltAndroidApp
class ThisDayInHistoryApp : Application(), ImageLoaderFactory {
    override fun newImageLoader(): ImageLoader {
        return ImageLoader.Builder(this)
            .logger(DebugLogger())
            .crossfade(true)
            .respectCacheHeaders(false)
            .memoryCache {
                MemoryCache.Builder(this)
                    .maxSizePercent(MAX_SIZE_PERCENT)
                    .build()
            }
            .diskCache {
                DiskCache.Builder()
                    .directory(cacheDir.resolve(CACHE_DIR))
                    .maxSizeBytes(MAX_BYTE_SIZE)
                    .build()
            }
            .build()
    }

    companion object {
        const val MAX_BYTE_SIZE = 5 * 1024 * 1024L
        const val MAX_SIZE_PERCENT = 0.25
        const val CACHE_DIR = "image_cache"
    }
}

MainActivity.kt

Modify MainActivity.kt in the com.coroutines.thisdayinhistory package in the app module, annotate it with @AndroidEntryPoint:


@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    //rest of code remains the same
    }
    

Manifest

Modify Manifest.xml by setting android:name=".ThisDayInHistoryApp" to point to the Hilt app:


<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_catapplogo"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_catapplogo_round"
        android:supportsRtl="true"
        android:name=".ThisDayInHistoryApp"
        android:theme="@style/Theme.App.Starting"
        android:localeConfig="@xml/locales_config"
        tools:targetApi="tiramisu">
        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:theme="@style/Theme.App.Starting">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
    

HistoryViewModel.kt

Modify HistoryViewModel.kt :


@HiltViewModel(assistedFactory = HistoryViewModel.IHistoryViewModelFactory::class)
class HistoryViewModel @AssistedInject constructor(
    @Assisted private val lang: LangEnum,
    private val historyDataUseCase: IHistoryDataStandardUseCase,
    private val historyDataMap: IHistoryDataMap,
    val historyCalendar: IHistoryCalendar
) : IHistoryViewModel,
    IHistoryCalendar by historyCalendar,
    IInternationalMonth by InternationalMonth(
        mutableStateOf(lang.langId),
        historyCalendar.monthOfCalendar
    ),
    ViewModel() {
        //previous code omitted for brevity
        // add the below:
    @AssistedFactory
    interface IHistoryViewModelFactory {
        fun create(language: LangEnum): HistoryViewModel
    }
        }