
Benchmarking Koin vs. Dagger Hilt in Modern Android Development (2024)
Benchmarking Koin vs. Dagger Hilt in Modern Android Development (2024) ź“ė Ø

When choosing a dependency injection framework for Android and Kotlin development, performance is often a key consideration. This article explores the performance ofKoinin its latest version (4.0.1-Beta1) and compares it withDagger Hilt (2.52). Rather than relying on simplistic benchmarks or limited code execution scenarios, the focus is ādeveloper-centricā: understanding performance in real-world, day-to-day usage. Additionally, this article aims to reassure those who may hesitate to adoptKoindue to performance concerns.

android/nowinandroid
)banner.What to benchmark?
Benchmarking such frameworks poses a significant challenge: ensuring fair comparison and focused on equivalent behaviors and features.
To make this exercise meaningful, Iāve opted for a user-oriented approach: evaluating the time it takes to build a component requested from the UI (like ViewModels and so on ā¦). To ensure our test context is strong enough, we need a complex enough application (no basic āHello Worldā or to-do list app).
For this purpose, Iāve chosen to use GoogleāsNow in Android app (android/nowinandroid
), a great open-source application that is complex enough and covers the challenges of real-life development and where the Android team demonstrates best practices (modularization, Jetpack Compose, and dependency injection ā¦).
By evaluating Koin and Dagger Hilt in this environment, we aim to get insights that truly matter to Android developers.
Note
š Sources are available atInsertKoinIO/nowinandroid
You will find the following branches:
perfs_koin
ā is the Now in Android migrated to Koin branch, with performances measurementperfs_hilt
ā is the default Hilt branch, with performances measurement
And donāt forgetofficial Koin documentation. Now, letās dive into the details!
Service Locator or Dependency Injection? Koin can do both!
Before diving into the benchmarks, letās address a common question about Koin: Is it a Service Locator or a Dependency Injection (DI) framework? The answer is both.
- AService Locatorretrieves dependencies dynamically through a centralized registry.
- Dependency Injectionprovides dependencies explicitly at instantiation, enhancing testability and maintainability.
Koin bridges these two approaches, offering dynamic retrieval viaget()
orinject()
while also supporting DI features like constructor injection and scoping.
Koinās dynamic behavior is influenced by Androidās lifecycle, which historically made constructor injection challenging. While modern Android features now support constructor injection, Koin remains flexible, letting developers choose the best approach for their needs.
At its core,Koin is a DI framework. It avoids reflection overhead, uses a Kotlin DSL for dependency graphs, and supports scoped lifecycles. However, its ability to function as a Service Locator adds versatility, particularly for simpler or legacy projects.
This is a summary, but this Koin projectdocumentation pagehas more details if you need to go deeper.
Why choose Koin?
- Simple and Developer-Friendly: Koinās clean DSL, no compile-time overhead, minimal setup, and easy testing let you focus on building your apps.
- Scales with Your App: from small apps to complex projects, Koin scales effortlessly to meet your needs.
- Evolving Compile-Time Safety: With features like module validation (Verify API),Koin Annotations(KSP for configuration safety), and the upcomingKoin IDE Plugin, Koin simplifies coding while boosting safety.
- Ready for Kotlin Multiplatform: Koin seamlessly manages dependencies across iOS, Android, Desktop, and Web, making it the go-to DI framework for cross-platform development.
- Perfect for Compose Multiplatform: Koin integrates effortlessly with Compose Multiplatform, supporting shared logic and DI for UI components ā evenViewModel.
If youāre curious about Koinās internals and design, let me know ā Iād be happy to explore that in a future article. For now, letās dive into the benchmarks! š
Tracking Performances
Tracking the performance of components over sessions is trickier than it initially seems. While tools likeBaseline Profiles Macrobenchmarkand similar deep-dive tools offer great analysis, they donāt allow me to easily extract benchmark values for custom use. Alternatively, connected dev platforms likeFirebase Crashlytics or Kotzilla Platformoffer convenient solutions to capture and analyze performance metrics.
My goal here is tostay simple and lightweight: I want tomeasure how long it takes to create a specific component, like building a ViewModel instance using dependency injection. I donāt need a complex framework for this task, but Iām OK with manually instrumenting my code as long as itās straightforward and lightweight.
To achieve this, I wrote a few functions to capture function call time from DI frameworks (All is inMeasure.kt
(InsertKoinIO/nowinandroid
)file). This utility leveragesKotlinās measureTimedValue function, an elegant and efficient way to measure code execution times, making it an excellent fit for lightweight, manual instrumentation. By extending the AndroidContext
, I created an easy way to log the duration of any function call (or dependency injection operation) directly to a log file.
inline fun <reified T> Context.measureTimeLazy(tag : String, code : () -> Lazy<T>) : Lazy<T>{
val result = measureTimedValue(code)
val timeInMs = result.duration.inWholeMicroseconds / 1000.0
logBenchmark(tag,timeInMs)
return result.value
}
In the end,we are storing all results in a local file(functionlogBenchmark). This file will be extracted, to allow average times calculation.
Now in Android
Now, letās see how these tracking functions are applied in our real-world scenario. For this benchmark, weāll measure the performance of the following components:MainActivityViewModel,ForYouViewModel, andstartup time.
These ViewModels are the first two used in the application, making them ideal candidates for assessing the performance of DI frameworks during the appās initial loading phase.
In theKoin implementation, the performance tracking for these components is instrumented as follows (MainActivity.kt
(InsertKoinIO/nowinandroid
)&ForYouScreen.kt
(InsertKoinIO/nowinandroid
) links):
class MainActivity : ComponentActivity() {
/**
* Lazily inject [JankStats], which is used to track jank throughout the app.
*/
val lazyStats by inject<JankStats> { parametersOf(this) }
val networkMonitor: NetworkMonitor by inject()
val timeZoneMonitor: TimeZoneMonitor by inject()
val analyticsHelper: AnalyticsHelper by inject()
val userNewsResourceRepository: UserNewsResourceRepository by inject()
private val viewModel: MainActivityViewModel by measureTimeLazy("MainActivityViewModel") { viewModel() }
// ...
}
@Composable
internal fun ForYouScreen(
onTopicClick: (String) -> Unit,
modifier: Modifier = Modifier,
viewModel: ForYouViewModel = LocalContext.current.measureTime("ForYouViewModel") { koinViewModel() },
) {
val onboardingUiState by viewModel.onboardingUiState.collectAsStateWithLifecycle()
val feedState by viewModel.feedState.collectAsStateWithLifecycle()
val isSyncing by viewModel.isSyncing.collectAsStateWithLifecycle()
val deepLinkedUserNewsResource by viewModel.deepLinkedNewsResource.collectAsStateWithLifecycle()
ForYouScreen(
isSyncing = isSyncing,
onboardingUiState = onboardingUiState,
feedState = feedState,
deepLinkedUserNewsResource = deepLinkedUserNewsResource,
onTopicCheckedChanged = viewModel::updateTopicSelection,
onDeepLinkOpened = viewModel::onDeepLinkOpened,
onTopicClick = onTopicClick,
saveFollowedTopics = viewModel::dismissOnboarding,
onNewsResourcesCheckedChanged = viewModel::updateNewsResourceSaved,
onNewsResourceViewed = { viewModel.setNewsResourceViewed(it, true) },
modifier = modifier,
)
}
Note
We are using the latestKoin AndroidX Startupfeature to help improve startup time.
For theHilt implementation, tracking is similarly applied (MainActivity.kt
(InsertKoinIO/nowinandroid
)&ForYouScreen
(InsertKoinIO/nowinandroid
)links):
To capture thestartup time, we use theonWindowFocusChanged
function inMainActivity. This measures the time it takes for the app to render its first frame after gaining focus, giving a clear picture of the appās startup performance. We track time from theApplicationclass until the first Activity:
class MainActivity : ComponentActivity() {
// ...
override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus)
if (hasFocus) {
val endTime = System.currentTimeMillis()
val startupTime = endTime - NiaApplication.startTime
logBenchmark("AppStartup",startupTime.toDouble())
}
}
// ...
}
Execution, Extraction, And Results
To capture performance metrics automatically, we run thebenchmark.sh
shell script. This script automates a sequence of app install, start, wait a few seconds, and stop actions to simulate realistic usage patterns. After all runs, it extracts thebenchmark_log.txt
file containing all recorded times. This is 25 iterations of running the Nia applicationās start, wait and stop (demo release build).
Using the collected data, thestats.py
Python script processes the log to compute key statistics: minimum, maximum, and average times for each benchmarked component.
On your terminal, you can just run the command:benchmark.sh; python3 stats.py
(from the /app
folder).
The best is to run it on a real Android device. On my OnePlus Nord (Android 12), I got the following results:
OnePlus Nordresults (arnaudgiuliani
), and also in Googlespreadsheet
Benchmark Results
Same OnePlus Nordresults (arnaudgiuliani
) in table
Component | Framework | Avg (ms) | Min (ms) | Max (ms) | Standard Error ( ms) |
---|---|---|---|---|---|
MainActivityViewModel | Koin | 0.166 | 0.146 | 0.198 | 0.002 |
MainActivityViewModel | Hilt | 0.202 | 0.186 | 0.264 | 0.003 |
ForYouViewModel | Koin | 2.052 | 0.223 | 9.042 | 0.302 |
ForYouViewModel | Hilt | 2.203 | 0.359 | 8.481 | 0.299 |
App Startup | Koin | 1416.360 | 1204.000 | 1746.000 | 37.072 |
App Startup | Hilt | 1511.480 | 1238.000 | 1729.000 | 35.457 |
In this benchmark, in addition to average, minimum, and maximum, we show the āstandard errorā: it measures thereliability of the average, indicating how much it may vary from the true population mean. Smaller values mean more stable and precise results. It helps also compare stability results between Koin and Dagger Hilt.
The benchmarks highlight Koinas a reliable and modern alternative for Android development, matchingHiltin performance while offering its own unique advantages.
That said, benchmarks are just one part of the story. Your results may vary depending on your app, but the trends are clear:Koin is performant for real-world challenges. From Android to Kotlin Multiplatform and Compose Multiplatform applications.
Iām always open to feedback ā if you have thoughts or insights,letās chat! š
Why not giveKoina shot? LetKoinbe part of your journey! š
Info
This article is previously published on proandroiddev
