Android Hilt Sample
This page provides an example of using Mill to build an Android application that utilizes Hilt for dependency injection, based on the official TODO app.
Android Mill Setup for building the TODO App
package build
import mill.*, androidlib.*, kotlinlib.*
import hilt.AndroidHiltSupport
object Versions {
val kotlinVersion = "2.0.21"
val kotlinLanguageVersion = "1.9"
val kspVersion = "1.0.28"
val androidCompileSdk = 35
val androidMinSdk = 26
}
Create and configure an Android SDK module to manage Android SDK paths and tools.
object androidSdkModule0 extends AndroidSdkModule {
def buildToolsVersion = "35.0.0"
}
Mill configuration for the Android Todo App project.
object app extends AndroidAppKotlinModule, AndroidR8AppModule, AndroidBuildConfig,
AndroidHiltSupport {
def kotlinVersion = Versions.kotlinVersion
def kotlinLanguageVersion = Versions.kotlinLanguageVersion
def kspVersion = Versions.kspVersion
override def androidDebugSettings: T[AndroidBuildTypeSettings] = Task {
AndroidBuildTypeSettings(
isMinifyEnabled = false,
isShrinkEnabled = false
).withDefaultProguardFile("proguard-android.txt")
.withProguardLocalFiles(
Seq(
moduleDir / "proguard-rules.pro"
)
)
}
// TODO consider using a debug module
def debugSources = Task.Sources("src/debug/java")
override def sources = super.sources() ++ debugSources()
def androidApplicationNamespace = "com.example.android.architecture.blueprints.todoapp"
// TODO change this to com.example.android.architecture.blueprints.main when mill supports build variants
def androidApplicationId = "com.example.android.architecture.blueprints.main"
def androidSdkModule = mill.api.ModuleRef(androidSdkModule0)
def androidCompileSdk = Versions.androidCompileSdk
def androidMinSdk = Versions.androidMinSdk
def androidEnableCompose = true
def androidIsDebug = true
def bomMvnDeps = Seq(
mvn"androidx.compose:compose-bom:2024.12.01"
)
def mvnDeps: T[Seq[Dep]] = super.mvnDeps() ++ Seq(
mvn"androidx.core:core-ktx:1.15.0",
mvn"androidx.appcompat:appcompat:1.7.0",
mvn"androidx.annotation:annotation:1.9.1",
mvn"org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0",
mvn"com.jakewharton.timber:timber:5.0.1",
mvn"androidx.test.espresso:espresso-idling-resource:3.6.1",
mvn"androidx.room:room-runtime:2.6.1",
mvn"androidx.room:room-ktx:2.6.1",
mvn"androidx.activity:activity-compose:1.10.0",
mvn"androidx.navigation:navigation-compose:2.8.5",
mvn"androidx.emoji2:emoji2:1.3.0",
mvn"androidx.lifecycle:lifecycle-common:2.8.7",
mvn"androidx.lifecycle:lifecycle-process:2.8.7",
mvn"androidx.lifecycle:lifecycle-runtime-compose:2.8.7",
mvn"androidx.lifecycle:lifecycle-viewmodel-compose:2.8.7",
mvn"androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.7",
mvn"com.google.accompanist:accompanist-appcompat-theme:0.36.0",
mvn"com.google.dagger:hilt-android:2.56",
mvn"androidx.hilt:hilt-navigation-compose:1.2.0",
mvn"com.google.accompanist:accompanist-swiperefresh:0.36.0",
mvn"androidx.customview:customview-poolingcontainer:1.0.0",
mvn"androidx.tracing:tracing:1.2.0",
// versions are resolved with compose-bom
mvn"androidx.compose.foundation:foundation",
mvn"androidx.compose.foundation:foundation-layout",
mvn"androidx.compose.animation:animation",
mvn"androidx.compose.material3:material3",
mvn"androidx.compose.material:material",
mvn"androidx.compose.material:material-icons-extended",
mvn"androidx.compose.ui:ui-tooling-preview",
mvn"androidx.compose.ui:ui",
mvn"androidx.compose.ui:ui-unit",
mvn"androidx.compose.ui:ui-text",
mvn"androidx.compose.ui:ui-graphics",
// debug
mvn"androidx.compose.ui:ui-tooling",
mvn"androidx.compose.ui:ui-test-manifest"
)
def kotlinSymbolProcessors: T[Seq[Dep]] = Seq(
mvn"androidx.room:room-compiler:2.6.1",
mvn"com.google.dagger:hilt-android-compiler:2.56"
)
object test extends AndroidAppKotlinTests, AndroidHiltSupport, TestModule.Junit4 {
def moduleDeps = super.moduleDeps ++ Seq(`shared-test`)
def androidEnableCompose = true
override def kspVersion = Versions.kspVersion
def kotlinSymbolProcessors: T[Seq[Dep]] = Seq(
mvn"androidx.room:room-compiler:2.6.1",
mvn"com.google.dagger:hilt-android-compiler:2.56"
)
def bomMvnDeps = Seq(
mvn"androidx.compose:compose-bom:2024.12.01"
)
// versions resolved from compose-bom
def composeDeps = Seq(
mvn"androidx.compose.ui:ui-test-junit4"
)
def mvnDeps = super.mvnDeps() ++ composeDeps ++ Seq(
mvn"junit:junit:4.13.2",
mvn"androidx.arch.core:core-testing:2.2.0",
mvn"org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0",
mvn"org.jetbrains.kotlinx:kotlinx-coroutines-test:1.9.0",
mvn"androidx.navigation:navigation-testing:2.8.5",
mvn"androidx.test.espresso:espresso-core:3.6.1",
mvn"androidx.test.espresso:espresso-contrib:3.6.1",
mvn"androidx.test.espresso:espresso-intents:3.6.1",
mvn"com.google.truth:truth:1.4.4",
mvn"com.google.dagger:hilt-android-testing:2.56"
)
}
object androidTest extends AndroidAppKotlinInstrumentedTests, AndroidR8AppModule,
AndroidHiltSupport {
override def kotlinLanguageVersion = Versions.kotlinLanguageVersion
def moduleDeps = super.moduleDeps ++ Seq(`shared-test`)
def testFramework = "com.example.android.architecture.blueprints.todoapp.CustomTestRunner"
def androidEnableCompose = true
override def kspVersion = Versions.kspVersion
override def androidDebugSettings: T[AndroidBuildTypeSettings] = Task {
AndroidBuildTypeSettings(
isMinifyEnabled = false,
isShrinkEnabled = false
).withDefaultProguardFile("proguard-android.txt")
.withProguardLocalFiles(
Seq(
moduleDir / "proguardTest-rules.pro",
moduleDir / "proguard-rules.pro"
)
)
}
def bomMvnDeps = Seq(
mvn"androidx.compose:compose-bom:2024.12.01"
)
// versions resolved from compose-bom
def composeDeps = Seq(
mvn"androidx.compose.ui:ui-test-junit4"
)
def mvnDeps = super.mvnDeps() ++ composeDeps ++ Seq(
// Dependencies for Android unit tests
mvn"junit:junit:4.13.2",
mvn"org.jetbrains.kotlinx:kotlinx-coroutines-test:1.9.0",
mvn"org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0",
// AndroidX Test - Instrumented testing
mvn"androidx.test:core-ktx:1.6.1",
mvn"androidx.test.ext:junit-ktx:1.2.1",
mvn"androidx.test:rules:1.6.1",
mvn"androidx.room:room-testing:2.6.1",
mvn"androidx.arch.core:core-testing:2.2.0",
mvn"androidx.navigation:navigation-testing:2.8.5",
mvn"androidx.test.espresso:espresso-core:3.6.1",
mvn"androidx.test.espresso:espresso-contrib:3.6.1",
mvn"androidx.test.espresso:espresso-intents:3.6.1",
mvn"androidx.test.espresso:espresso-idling-resource:3.6.1",
mvn"androidx.test.espresso.idling:idling-concurrent:3.6.1",
// AndroidX Test - Hilt testing
mvn"com.google.dagger:hilt-android-testing:2.56"
)
def kotlinSymbolProcessors: T[Seq[Dep]] = Seq(
mvn"androidx.room:room-compiler:2.6.1",
mvn"com.google.dagger:hilt-android-compiler:2.56"
)
}
}
object `shared-test` extends AndroidKotlinModule, AndroidHiltSupport {
def moduleDeps = Seq(app)
def kotlinVersion = Versions.kotlinVersion
def kotlinLanguageVersion = Versions.kotlinLanguageVersion
def kspVersion = Versions.kspVersion
def androidIsDebug = true
def androidSdkModule = mill.api.ModuleRef(androidSdkModule0)
def androidCompileSdk = Versions.androidCompileSdk
def androidMinSdk = Versions.androidMinSdk
def androidEnableCompose = true
def androidNamespace = "com.example.android.architecture.blueprints.todoapp.daemon.test"
def kotlinSymbolProcessors: T[Seq[Dep]] = Seq(
mvn"androidx.room:room-compiler:2.6.1",
mvn"com.google.dagger:hilt-android-compiler:2.56"
)
def mvnDeps = super.mvnDeps() ++ Seq(
mvn"junit:junit:4.13.2",
mvn"androidx.arch.core:core-testing:2.2.0",
mvn"org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0",
mvn"org.jetbrains.kotlinx:kotlinx-coroutines-test:1.9.0",
mvn"androidx.test:core-ktx:1.6.1",
mvn"androidx.test.ext:junit-ktx:1.2.1",
mvn"androidx.test:rules:1.6.1",
mvn"com.google.dagger:hilt-android:2.56",
mvn"com.google.dagger:hilt-android-testing:2.56",
mvn"androidx.room:room-runtime:2.6.1",
mvn"androidx.room:room-ktx:2.6.1"
)
}
> ./mill app.androidApk
> ./mill app.test
> cat out/app/test/testForked.dest/test-report.xml
<?xml version='1.0' encoding='UTF-8'?>
<testsuites tests="46" failures="0" errors="0" skipped="0" time="...">
...
> ./mill show app.createAndroidVirtualDevice
...Name: test, DeviceId: medium_phone...
> ./mill show app.startAndroidEmulator
> ./mill show app.androidInstall
...All files should be loaded. Notifying the device...
> ./mill show app.androidRun --activity com.example.android.architecture.blueprints.todoapp.TodoActivity
[
"Starting: Intent { cmp=com.example.android.architecture.blueprints.main/com.example.android.architecture.blueprints.todoapp.TodoActivity }",
"Status: ok",
"LaunchState: COLD",
"Activity: com.example.android.architecture.blueprints.main/com.example.android.architecture.blueprints.todoapp.TodoActivity",
"TotalTime: ...",
"WaitTime: ...",
"Complete"
]
> ./mill app.androidTest
> cat out/app/androidTest/testForked.dest/test-report.xml
<?xml version='1.0' encoding='UTF-8'?>
<testsuites tests="38" failures="0" errors="0" skipped="0" time="...">
...
> ./mill show app.stopAndroidEmulator
> ./mill show app.deleteAndroidVirtualDevice
This example demonstrates how to build an Android app that uses Hilt for dependency injection, translating the original Gradle configuration to Mill.