Blog Infos
Author
Published
Topics
, , , ,
Published

In Android development, Hilt is often the go-to library for dependency injection due to its official support from Google and deep integration with Android libraries. However, I prefer Koin for its simplicity, fast adoption and Kotlin-first approach. In my experience, Koin’s lightweight design makes it easier to set up and maintain, while providing a powerful DI solution that doesn’t compromise on flexibility. Let’s explore why Koin is my favorite DI library and how you can use it effectively in your Android projects.

Why Koin?

There are a number of reasons why I love Koin. First, it’s easy to use. Koin has a simple and intuitive API that makes it easy to get started with dependency injection. Second, Koin is lightweight. It won’t add a lot of overhead to your application. Third, Koin is powerful. It provides a powerful set of features that can help you manage dependencies in complex applications. Finally, Koin offers direct integrations with Jetpack Compose.

Getting Started with Koin

Getting started with Koin is easy. You can add the Koin library to your project using the version catalog and the gradle file.

//libs.versions.toml
[versions]
koin = "4.0.0"
...
[libraries]
koin-android = { module = "io.insert-koin:koin-android", version.ref = "koin" }
koin-androidx-compose = { module = "io.insert-koin:koin-androidx-compose", version.ref = "koin" }
...

//build.gradle.kts    
implementation(libs.koin.androidx.compose)
implementation(libs.koin.android)
...

Once you’ve added the libraries, you can start defining your dependencies. Koin uses modules to define dependencies. A module is a collection of dependencies that can be used together. Here’s an example:

val myModules = module {
    single { MyHttpClient().getClient() }
    factory { BookService(get()) as IBookService }
    factoryOf(::BookDownloader)
    single { BookDatabase.getInstance(get()) }
    factoryOf(::DatabaseRepository)
    factoryOf(::BookRepository)
    singleOf(::PrefsDataStore)

    viewModelOf(::NavigationViewModel)
    viewModelOf(::HomeViewModel)
    viewModelOf(::LibraryViewModel)
    viewModelOf(::ZoomBookViewModel)
    viewModelOf(::ReaderEpubViewModel)
    viewModelOf(::LanguagesSelectorViewModel)
    viewModelOf(::TopicViewModel)
    viewModelOf(::SettingsViewModel)

}

Within a module, you can declare various types of components:

  • Single: Provides a single instance of a dependency throughout the application’s lifecycle.
  • Factory: Creates a new instance of a dependency each time it’s requested.
  • ViewModel: Specifically designed for Android ViewModel instances, ensuring proper lifecycle management.

With the standard component definition you can also use the extensions functions (singleOf, factoryOf and viewModelOf ) to provide a more concise syntax for creating instances.

Initializing Koin Application

Once you’ve defined your modules, it’s time to integrate Koin with your application.

class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()

        startKoin {
            androidLogger()
            androidContext(applicationContext)
            modules(myModules)
        }
    }
}

In your AndroidManifest.xml file, update the application tag to reference your custom application class:

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

    <application
        android:name=".MyApplication"
...

By following these steps, you’ve successfully initialized Koin and made it ready to manage your application’s dependencies.

Injecting Dependencies

Koin makes it straightforward to inject dependencies into your classes. By defining your modules and components, you can directly access them within your classes.

Here’s an example of a HomeViewModel using dependencies provided by Koin:

class HomeViewModel(
    private val bookRepository: BookRepository,
    prefsDataStore: PrefsDataStore,
    private val gutenbergRepository: GutenbergRepository,
) : ViewModel() {
    // ViewModel logic here
}

For a Jetpack Compose environment, the setup is slightly different. In your Activity, it’s helpful to wrap all Compose code inside a KoinAndroidContext to define the Koin Context and then use the dependencies:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyBooksTheme {
                KoinAndroidContext {
                    val navigationViewModel = koinViewModel<NavigationViewModel>()
                    // Additional UI code here
                }
            }
        }
    }
}

In your Composable functions, you can then easily inject your ViewModels with Koin:

@Composable
fun HomeInitScreen(
    navigationViewModel: NavigationViewModel = koinViewModel(viewModelStoreOwner = LocalContext.current as ComponentActivity),
    homeViewModel: HomeViewModel = koinViewModel(),
) {
    // UI code here
}

In this example:

  • NavigationViewModel will follow the lifecycle of the Activity, making it suitable for app-wide navigation.
  • HomeViewModel will be scoped to the HomeInitScreen Composable function, fitting the more transient lifecycle of UI elements in Compose.

This setup allows for precise lifecycle management with minimal boilerplate, making Koin an excellent choice for Compose-based projects.

Testing with Koin

Koin’s structure makes it highly testable and ideal for achieving high code coverage. Dependencies can be easily mocked or replaced, and you can even define custom Koin modules specifically for testing purposes.

Kotlin class MyTest: KoinTest { 
  @Before 
  fun setup() { 
    startkoin ( modules (testModule)) 
  } 

  @After 
  fun tearDown() { 
    stopKoin() 
  } 
}

This flexibility allows you to isolate and test individual components effectively.

To ensure that your Koin modules are configured correctly, you can use the verify extension provided by Koin’s testing utilities:

class MyModulesTest: KoinTest {

    @OptIn(KoinExperimentalAPI::class)
    @Test
    fun testMyModules() {
        myModules.verify(
            extraTypes = listOf(
                Application::class,
                Context::class,
            )
        )
    }
}

This test verifies the correctness of the myModules definition, ensuring that all dependencies are defined and can be resolved.

By following these guidelines, you can effectively test your Koin-based applications, improving code quality and reducing potential issues.

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

, ,

Kobweb:Creating websites in Kotlin leveraging Compose HTML

Kobweb is a Kotlin web framework that aims to make web development enjoyable by building on top of Compose HTML and drawing inspiration from Jetpack Compose.
Watch Video

Kobweb:Creating websites in Kotlin leveraging Compose HTML

David Herman
Ex-Googler, author of Kobweb

Kobweb:Creating websites in Kotlin leveraging Compose HTML

David Herman
Ex-Googler, author o ...

Kobweb:Creating websites in Kotlin leveraging Compose HTML

David Herman
Ex-Googler, author of Kob ...

Jobs

Koin Annotations: A Hilt-like Approach

If you prefer Hilt’s annotation-based approach, Koin recently introduced annotations, allowing you to mark dependencies with simple annotations for a more declarative setup. This style lets you define dependencies similarly to Hilt.

One key difference is that Koin traditionally resolves dependencies at runtime. This means that if a dependency is missing, the error only appears when the dependency is requested. With the new annotation-based approach, Koin now offers compile-time checking for dependencies, which catches these issues during the build process. This extra validation is especially valuable for large apps with complex dependency trees, adding a layer of robustness and reliability to your project.

To update the implementation we already defined to Koin annotations, follow these steps:

  • Include the Kotlin Symbol Processing (KSP) plugin:
//libs.versions.toml
[versions]
ksp = "2.0.20-1.0.25"
koinAnnotations = "2.0.0-Beta1"
...
[plugins]
googleDevtoolsKsp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
...

//app build.gradle.kts
plugins {
    alias(libs.plugins.googleDevtoolsKsp)
  ...
  • Include the extra Koin dependencies for annotations, and the KSP compiler:
//libs.versions.toml
[versions]
koinAnnotations = "2.0.0-Beta1"
...
[libraries]
koin-annotations = { module = "io.insert-koin:koin-annotations", version.ref = "koinAnnotations" }
koin-ksp-compiler = { module = "io.insert-koin:koin-ksp-compiler", version.ref = "koinAnnotations" }
...


//app build.gradle.kts
dependencies {
    ksp(libs.koin.ksp.compiler)
    implementation(libs.koin.annotations)
  ...
  • Enable compile-time safety checks in your build.gradle.kts:
ksp {
    arg("KOIN_CONFIG_CHECK", "true")
}

With this configuration in place, you can remove your modules definition:

import org.koin.ksp.generated.*


class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()

        startKoin {
            androidLogger()
            androidContext(applicationContext)
            modules(defaultModule)
        }
    }
}

and start migrating your dependencies by using Koin’s annotations: @Single@Factory, and @KoinViewModel. Simply add the relevant annotation on top of each class to define its lifecycle and scope.

For example:


@Single
class PrefsDataStore(private val context: Context) {
    //logic here
}
@Factory
class BookRepository(
    private val bookService: IBookService, private val prefsDataStore: PrefsDataStore
) {
    //logic here
}

@KoinViewModel
class HomeViewModel(
    private val bookRepository: BookRepository,
    prefsDataStore: PrefsDataStore,
    private val gutenbergRepository: GutenbergRepository,
) : ViewModel() {
    // ViewModel logic here
}

Once you’ve annotated your dependencies, Koin will verify them at compile time. If any dependencies are missing or misconfigured, you’ll receive an error during the build process, allowing you to catch issues early. This compile-time validation makes your app more robust, especially as it scales and new dependencies are added.

Migrating from Hilt to Koin

If you’re convinced about Koin’s benefits and want to migrate your Hilt project, a helpful guide can be found here:
https://blog.kotzilla.io/migrate-from-hilt-to-koin

This guide walks you through the process of translating Hilt annotations to their Koin counterparts.

Conclusion

Now you know why Koin is my favorite Dependency Injection library for Android. I really like its simplicity, flexibility, and Kotlin-centric approach. Its easy setup and clear lifecycle management make it ideal for both small and large applications.

With Koin, you have the flexibility to define dependencies in a straightforward syntax, while also benefiting from recent advancements like annotation support and compile-time checks. These features allow you to ensure dependency correctness during the build process, enhancing the reliability of your app as it grows in complexity.

Whether you’re building a new project or considering a DI solution for an existing one, Koin’s simplicity and power make it an excellent choice. By following the guidelines in this article, you can take advantage of Koin’s capabilities to create a clean, maintainable, and testable codebase for your Android apps.

If you found this article interesting, feel free to follow me for more insightful content on Android development and Jetpack Compose. I publish new articles almost every week. Don’t hesitate to share your comments or reach out to me on LinkedIn for further discussions.

Have a great day!

This article is previously published on proandroiddev.com

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
It’s one of the common UX across apps to provide swipe to dismiss so…
READ MORE
blog
This is the second article in an article series that will discuss the dependency…
READ MORE
blog
Hi, today I come to you with a quick tip on how to update…
READ MORE
blog
Automation is a key point of Software Testing once it make possible to reproduce…
READ MORE
Menu