Getting to Know Koin Annotations

Pedro Francisco de Sousa Neto
Koin developers
Published in
4 min readMar 22, 2024

--

Cover photo by Pawel Czerwinski on Unsplash

Declaring Dependencies with Koin

Through this article, you will learn all the ways to declare dependencies using Koin — Koin DSL, Constructor DSL, and finally the powerful Koin Annotations.

Spoiler alert: Koin can check your dependencies in compile time!

Koin DSL

This is how we declare the Koin module and initialize constructors explicitly, invoking the famous get() from Koin.

Example:

class ClassA()
class ClassB(val a: ClassA)

val myModule = module {
single { ClassA() }
single { ClassB(a = get()) }
}

Constructor DSL

This way, the work becomes much simpler, as we do not need to explicitly declare the parameters of the constructors.

Example:

class ClassA()
class ClassB(val a: ClassA)

val myModule = module {
singleOf(::ClassA)
singleOf(::ClassB)
}

Annotations

Finally, we arrive where the magic truly happens, as we can stop using (or not) the declaration of dependencies in a Koin module, simply by using annotations on the classes of our dependencies.

Example:

@Single
class ClassA()

@Factory
class ClassB(val a: ClassA)

@KoinViewModel
class MyViewModel(b: ClassB): ViewModel()

@KoinWorker
class UploadFiles: WorkManager()

It’s also possible to use scopes and properties in your annotations. For more details, consult the Koin Annotations guide here.

Koin Annotations

So far, I believe you have already realized how powerful Koin Annotations is, and also how similar it is to other players in the market, such as Dagger and Hilt, right?

Koin Annotations works in addition to Kotlin projects that make use of Koin. It can be integrated into existing projects as well as new projects.

With the adoption of Koin Annotations, not only do we simplify the code by using annotations, but we can also use a feature that is often the reason for many complaints against using Koin, which is dependency verification at compile time!

Compile Safety

With this feature, it is possible to validate all dependencies at compile time just like Hilt or Dagger does. You only need to add a line of code ksp { arg("KOIN_CONFIG_CHECK", "true") }.

According to Kotzilla, the company responsible for maintaining and distributing Koin, the compilation time of Koin Annotations is 75% faster than Dagger.

Compile safety is still an experimental feature. However, I have been using it in a Server-Driven UI project, in the core of the SDK to be used by the Android client-side, for over a month without any issues.

It’s worth noting that compile safety is configurable, so it can be enabled or disabled at any time via the Gradle file — the default is false/disabled.

One downside, in my opinion, is that compile safety cannot see dependencies declared in Koin DSL or Constructor DSL.

Below is an example of an error when trying to compile a project with the feature enabled and an unmapped unannotated dependency.

Koin Compile Safety failing your build because one dependency is missing

Troubleshooting: Koin couldn’t satisfy the dependency of ClassA that was expected in the constructor of ClassB.

But what about my modules?

Under the hood, Koin Annotations will generate a module containing all mapped dependencies. And to use this module in your traditional startKoin is very simple, for example:

// Use the module default generated by Koin Annotations
import org.koin.ksp.generated.*

fun main() {
startKoin { defaultModule() }
}

// Scenario with N modules
fun main() {
startKoin { modules(defaultModule, moduleA, moduleB, existingModule) }
}

But what about the organization?

If you think about a very large project with many dependencies, the default module may not sound like a very organized approach. To prevent Koin Annotations from generating this module, simply disable this default setting with ksp { arg("KOIN_DEFAULT_MODULE", "false") }.

In this case, we should start declaring the use of modules generated by annotations. See the examples below.

Koin Module with Koin Annotations:

@Module
class MyGiantModule

Using the generated module:

import org.koin.ksp.generated.*

fun main() {
startKoin { modules(MyGiantModule().module) }
}

How to declare dependencies for each @Module?

Now let’s look at another annotation for your module, the @ComponentScan. It will cause all dependencies declared in the same package and subpackages to be mapped to your @Module. See the example below:

@Module
@ComponentScan
class MyModule

You can also strictly specify a package to be watched and bind dependencies.

@Module
@ComponentScan("com.my.app")
class MyModule

It is still possible to declare direct dependencies in your annotated module using Kotlin functions.

@Module
@ComponentScan("com.my.app")
class MyModule {

@Factory
fun service(retrofit: Retrofit): ServiceAPI {
return retrofit.create(ServiceAPI::class.java)
}

}

Module inclusion

The inclusion of modules works with annotations too, as follows:

@Module
class ModuleA

@Module(includes = [ModuleA::class])
class ModuleB

Usage of annotated modules

Just like was.

import org.koin.ksp.generated.*

fun main() {
startKoin {
modules(
ModuleB().module
)
}
}

Conclusion

Koin Annotations can be used in both new and existing projects. Moreover, it provides a very idiomatic way to work with dependency injection in Kotlin.

I believe this was the last step needed for Koin to dazzle any Kotlin developer!

And just one more thing, Koin Annotations is not specific to the Android platform; it also works in multi-platform projects.

By the way, here is a very good article about Koin Annotations written by Jacob Ras, Migrating to Koin Annotations in a multiplatform project.

Next Steps

There are still more topics to be covered regarding Koin Annotations, such as specifying the binding of a dependency, injecting a parameter, injecting a lazy dependency, obtaining a list of dependencies of the same type, etc.

For these and other topics, please refer to the official documentation of Koin Annotations.

I will soon publish a new article demonstrating how to add Koin Annotations to just one project module, showcasing the flexibility we have to evolve a project already using Koin.

--

--