Please fasten your seatbelts, the flight time today will be approximately a few minutes. Source: Unsplash
Have you ever found yourself traveling with friends on a low-cost carrier, choosing not to pay extra for seat selection, only to end up scattered all over the airplane? I have, and that’s why I created Flight Chat — a small app designed to help you stay in touch during flights. It’s available on the Play Store, and I’m hoping to launch it soon on the App Store for my iPhone friends…
But before supporting iOS, the app needs Kotlin Multiplatform (KMP) compatibility to remain “as native as possible”, which is especially important for BLE-related features. In my previous article about the recommended Android tech stack (as of 2022), I selected Hilt as my DI framework of choice. Over time, it proved solid — we rewrote a 10M+ downloads app without running into significant DI issues. However, the major drawback is that Hilt does not support KMP. And likely won’t for the foreseeable future.
Since Hilt isn’t a viable option for multi-platform development, I looked into other libraries written fully in Kotlin that offer KMP support. I decided to try Koin first to see how it fares. Why Koin? Let me quote myself from that previous article:
All the libraries in the previous sections are being created by the Square, Google or Kotlin team. So there is a big chance they will not be deprecated overnight, you may find them in multiple projects so they will be battle-proven in many (also large-scale) production apps, and when having an issue — there will be an answer on StackOverflow.
Koin, on the other hand, isn’t made by those same companies, but it’s backed by the Kotzilla team — which, on paper, seems more promising than single-contributor projects without such support. Besides, Koin has been around in the Android community for quite some time; I even used it in my first article back in 2019. It also leads in popularity on GitHub with 9.1k starts, compared to competing libraries with 3.2k and 1.3k stars respectively. While GitHub stars aren’t the only metric, they do provide a quick and easy way to compare.
With new library chosen, we’re now ready to take-off ✈️.
Step 1 — Choose Necessary Koin Dependencies
Koin has become quite modular in terms of dependencies selection — enough to feel overwhelming for some:
Since my app isn’t KMP-ready yet, I decided to use koin-android
, koin-core
, koin-compose-viewmodel
, and koin-startup
. The most significant decision, however, was choosing between the default Koin DSL and Koin Annotations. As the name implies, Koin Annotations lets you declare your components and modules through annotations. For someone with a long history of using Dagger-like APIs, opting for Koin Annotations alongside these other dependencies was a no-brainer.
Step 2 — Migrate the Codebase Module by Module
It is usually best to migrate progressively in a larger team to keep the app functional throughout the transition. There are also a few helper methods available to bridge Hilt and Koin, making the process smoother. However, since I’m working on the project solo (and prefer not to learn additional APIs for a short-term solution), I opted for the “hard way”: migrating the entire codebase module by module, and only building the project at the very end.
I won’t repeat the details on how to map each Hilt annotation to its Koin Annotations counterpart — there’s already an official guide titled How To Migrate from Hilt to Koin — A Detailed Guide.
Step 3 — Fix Migration Compile-Time Issues
Once the migration was complete, it was time to attempt the first build. As expected, it failed — the amount of code change was substantial, after all. Below are some issues I encountered while fixing my app; your specific challenges may vary:
- A few classes needed to be
public
instead ofinternal
. - If a dependency isn’t provided inside the Module (but is annotated and scoped at its declaration), you still need to create an (empty) Module with
@ComponentScan
annotation in the same package. - App’s broadcast receivers (like these used for local notifications) need to implement
KoinComponent
. - Certain automatic bindings, like custom
Timber
trees that depend on a build variant type, might not be what you’d expect. - Double-check you’ve re-added the correct
Lazy
imports if needed!
Additionally, if you define navigation in a multi-module project like I did in the previous article, specifically by placing navigation factories in each feature module that implement an interface located in a core
sub-module, you may encounter dex merge conflicts. For example, :app:mergeLibDexDebug
can fail due to binding name conflicts in feature modules.
The only workaround I’ve found is to avoid auto-generated bindings and define them manually using the Koin DSLs:
internal val navigationFactoryModule = module { | |
single { LoginNavigationFactory() } bind NavigationFactory::class | |
single { SearchNavigationFactory(get()) } bind NavigationFactory::class | |
single { SettingsNavigationFactory(get()) } bind NavigationFactory::class | |
single { ChatNavigationFactory(get()) } bind NavigationFactory::class | |
} |
Job Offers
Step 4 — Launch the App and Observe a Run-Time Crash?
After some compile-time turbulence, and reading various online opinions claiming that “Koin will casually crash at run-time”, I assumed my migration landing will be rough. But when I hit “Run” in Android Studio, to my surprise, the app launched without a hitch. No crash at startup, no errors in Logcat. I tried some quick smoke tests, then did regression testing on my own — still nothing. The app worked exactly as it did before the migration.
Maybe I was just lucky, or maybe it was because my seat belt was fastened for the entire flight.
Summary
Today’s flight was quick and hassle-free — and I hope yours goes just as smoothly. Depending on the complexity of your codebase and the size of your team, you might choose a different migration strategy. But for my small, up-to-date app, the experience turned out to be surprisingly fine. If you’re also transitioning from a Hilt-based setup to Kotlin Multiplatform, I hope this article helps guide the way.
Wishing everyone a peaceful time with loved ones, and a happy year 2025!
. . .
Thanks to Maksym Syniutka for his valuable feedback.
This article is previously published on proandroiddev.com.