Enjoy Dependency Injection on Android with Koin

nathan kayumbanathan kayumba
3 min read

What is Koin?

Koin is a lightweight dependency injection (DI) framework written in Kotlin. It’s designed to be simple and easy to use, especially when building Android apps — whether you're using Jetpack Compose or not.

If you have no clue what dependency injection is, you might get a little lost here 😅. I recommend reading this other article I wrote first 👉 link .

But if you're still here, it's either because:

But if you're still here, it's either because:

  • You already know what DI is, or

  • You're just curious (which is a great trait for a developer!).

Why use Koin?

When you're just starting out, doing manual dependency injection in a small app might work. But as soon as your project grows, it quickly becomes a nightmare.

I’m speaking from experience — I used to waste so much time wiring objects together, figuring out who depends on what, and where to place each thing. Not fun. That’s not what I love doing when building apps.

I want to focus on real features, UI, and creating value — not spending time on the plumbing.

That’s exactly where Koin saves the day. It simplifies DI, reduces boilerplate, and helps me code faster and cleaner.

🙏 Big thanks to the people behind Koin. This framework helps me stay productive and makes coding way more fun.

Let’s get our hands dirty 🛠️

You can find the official docs here 👉 https://insert-koin.io/. It’s way more friendly and clear than Hilt’s docs, if you ask me.

1. Setup 📦

First, head to the Setup page and add the Koin dependencies in your module's build.gradle:

dependencies {
    implementation("io.insert-koin:koin-android:$koin_android_version")
    // Java Compatibility
    implementation("io.insert-koin:koin-android-compat:$koin_android_version")
    // Jetpack WorkManager
    implementation("io.insert-koin:koin-androidx-workmanager:$koin_android_version")
    // Navigation Graph
    implementation("io.insert-koin:koin-androidx-navigation:$koin_android_version")
    // App Startup
    implementation("io.insert-koin:koin-androidx-startup:$koin_android_version")

    // Jetpack Compose 
    implementation("io.insert-koin:koin-compose:$koin_version")
    implementation("io.insert-koin:koin-compose-viewmodel:$koin_version")
    implementation("io.insert-koin:koin-compose-viewmodel-navigation:$koin_version")
}

A full real-world example

Here’s how I used Koin in a real Android app — an app to save and display your favorite thoughts/quotes. Check it out on GitHub: QuoteApp

🔧 Koin Module (AppModule)

val appModule = module {
   single { QuoteDatabase.getInstance(get()) }
   single { get<QuoteDatabase>().quotesDao() }
   factory { QuotesRepository(get()) }
   viewModel { QuoteViewModel(get()) }
}

This is how you link each class to its instance creation strategy (single, factory, viewModel…).

📦 Classes to inject

@Database(entities = [EntityQuotes::class], version = 1)
abstract class QuoteDatabase : RoomDatabase() {
    abstract fun quotesDao(): QuotesDao
}

@Dao
interface QuotesDao

class QuotesRepository(private val quotesDao: QuotesDao)

class QuoteViewModel(private val quotesRepository: QuotesRepository) : ViewModel()

Simple, right? Now let’s configure the app to use this module.

Application setup

@OptIn(KoinExperimentalAPI::class)
class QuotesApplication : Application(), KoinStartup {
    override fun onKoinStartup() = KoinConfiguration {
        androidContext(this@QuotesApplication)
        modules(appModule)
    }

    override fun onCreate() {
        super.onCreate()
    }
}

And don’t forget to declare this in your AndroidManifest.xml:

<application
   android:name=".QuotesApplication"
   ... />

Using your ViewModel in a Composable

In your Composable screen:

val quoteViewModel: QuoteViewModel = koinViewModel()

No need to do anything else. Easy and clean 👌

Quick Reminder: single, factory, viewModel — What’s the difference?

  • single {}: A singleton — one instance shared across the app.

  • factory {}: Creates a new instance every time it’s injected.

  • viewModel {}: A ViewModel tied to the lifecycle — works seamlessly with Compose.

Other useful options with Koin

Here are a few more helpful Koin functions:

startKoin {
   logger()
   modules(appModule)

}

And inside a module:

module {
   single { MyRepository(get()) }
   factory { MyService(get()) }
   scope<MyActivity> {
       scoped { MyScopedService() }
   }
}

Conclusion

There you go! Now you’re ready to start using Koin like a pro, whether you're a beginner or more advanced.

No more wasting time on manual DI. Focus on building awesome features instead 🚀

Got questions or ideas? Drop a comment, I’d love to chat. See you next time 👋

0
Subscribe to my newsletter

Read articles from nathan kayumba directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

nathan kayumba
nathan kayumba

I'm a passionate software engineer with a strong focus on backend development and mobile app creation. I specialize in building native Android applications using Kotlin and Jetpack Compose. With a background in software engineering, I enjoy solving complex problems and creating seamless user experiences. When I'm not coding, you'll find me immersed in music, constantly seeking inspiration from new sounds. Let's build the future, one app at a time!