All Ruby on Rails Node JS Android iOS React Native Frontend

Koin as an Alternative to Dagger 2

A couple of assumptions for this article:

  1. To fully understand this article reader should have at least basic knowledge about Dagger 2 library.
  2. To simplify things, later on, I will refer to Dagger 2 library just as Dagger.
  3. I used here as example MVP architecture but any other could be used with either of those libraries.

Modules

The best place to start in my opinion is a module, as in that component we provide definitions of what we add to the dependency graph and how we should create and keep them.

The module in Dagger uses mainly annotations to declare its parts (as it is in other components of this library). So the class is declared with a @Module and methods to provide instances to the dependency graph that are marked with @Provides annotations.

Of course, that is the basic way of declaring it. Dagger is a mature library with many features which are handy in certain cases. In this article, I won't go into details for each of them as it’s not the purpose of it.

Each provider methods by default will be called and create an instance of object it provides, every time we will call Dagger for that type of class. That is of course if we don't use one of the annotations that combine with @Provides, as @Singleton which will create an object only first time it is called and after that will just share it. There are also scope annotations that change it but that will be covered later.

Koin uses DSL (domain-specific language, great articles about Kotlin DSL). We declare our module by creating a variable of it with “module” function. It has a couple of optional parameters (like “createOnStart” to indicate to create module definition with Koin’s start) but the most important is definition: ModuleDefinition.() -> Unit which is a block of code where we declare our providers. 

val appModule = module {
    // single instance of HelloRepository (which will be reused)
    single { HelloRepositoryImpl() }

    // Simple Presenter Factory (creates every time new)
    factory { MySimplePresenter(get()) }
}

Providers are also functions and similarly to Dagger, we can declare here if we want to create a new instance of object each time it is injected (factory {...}) or do we want a singleton (single {...}). Of course, as in Dagger, we can also declare scopes for them, which bounds they lifetime with a scope but that will be covered later.

On Koin web page we can read that they don’t use a proxy, code generation or reflection, but it’s not 100% truth. They added a feature that can simplify above module example using reflection.

val appModule = module {
    // single instance of HelloRepository
    singleBy<HelloRepository, HelloRepositoryImpl>()

    // Simple Presenter Factory
    factory<MySimplePresenter>()
}

It looks a little bit better but I will leave it up to you to decide if it is worth of using reflection.

Initialization

In Dagger, we need to initialize a proper component in our Application class. Components are interfaces based on which are generated proper Dagger Component classes. By builder, we can instantiate that class and grant access to all providers that were added within modules to this component. 

Koin is initialized too in Application class but doesn't need any Component interface declarations.

override fun onCreate() {
    super.onCreate()
    // start Koin context
    startKoin(this, listOf(appModule))
}

It’s started by the method “startKoin” like in the code above with two obligatory parameters that are Android context and list of modules to add. It looks and feels a lot simpler than Dagger.

companion object {
fun getComponent(context: Context) =
(context.applicationContext as WTWApplication).appComponent
}

lateinit var appComponent: AppComponent

override fun onCreate() {
super.onCreate()
appComponent = DaggerAppComponent
.builder()
.application(this)
.build()

}

Retrieving components

So we described our modules with all providers we need and initialized starting point. It’s time to actually use all those things. 

With Dagger, we can use ActivityInjector feature to simplify a little bit that process, combining with declaring injection in Presenter constructor we could get rid off declaring SubComponent and Module for each Activity (great example article how to do that).

Ok, but even with those we need to bind our Activity in ActivityBuilder and then call in Activity AndroidInjection.inject(this) in the onCreate method, to be able to inject things (again by annotation) @Inject in your class.

And here again, Koin is a little bit simpler, as we can inject things in Activities, Fragments, and Services out of the box, with just one line:

// Inject MyPresenter
val presenter : MyPresenter by inject()


And if you want to inject something in class that is none of those three (Activitie, Fragment, or Service) then just tag it with KoinComponent interface and that's it!

Scopes

Finally, we came back to scopes. If you know scopes from Dagger you can skip the next paragraph.

Scopes in Dagger are declared by Annotation (not a surprise). But that Annotations are not something that the library provides. The user declares what scopes he wants (eg. ActivityScope) then create the annotation and annotate component that “live” in that scope. Then add that annotation to all components that are provided to the dependency graph, it could be either in a module in the provider method or in the class itself. Those instances will be held as singletons as long as the component with annotation it shares is alive. For more information go to “Singletons and Scoped Bindings” section on Dagger guide. 

In Koin scopes are really similar in concept as they too bind lifetime of some instance with some scope.

Another similarity is that it is defined for providers in modules like on an example below.

// A scoped Presenter
module {
    scope("activity") { Presenter() }
}

 

But the way of binding is different. We don’t bind it with component life as in Dagger. Actually, there are two ways of defining it. First is only acceptable to Android LifecycleOwner.

class MyActivity : AppCompatActivity() {

    // inject Presenter instance, tied to current MyActivity's scope
    val presenter : Presenter by inject()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // bind current lifecycle to Activity's scope
        bindScope(createScope("activity"))
}

Which by default kills scope on Lifecycle.Event.ON_DESTROY but this can be overridden by the second parameter of bindScope function by any other Lifecycle.Event.

The second one is for sharing scope between components (eg. for sharing UserSession or Bucket in shopping application).  So the module part doesn’t change.

module {
    // Shared user session data
    scope("session") { UserSession() }
}

But you don't have to  bind it with LifecycleOwner just when you want to start scope call:

getKoin().createScope("session")

and when you want to end it just call:

getKoin().getScope("session").close()

Summarize

There are many other features that can be compared like support for Android Architecture Component ViewModel which both libraries have, but maybe it’s better to leave it for another article and focus on the global picture of that compare.

Dagger is based on annotations and code generation. It’s more mature with a lot of features and things can be done for a couple of different ways with the same good result. But overall with all those features, it feels more complex and can be scary for newcomers.

Koin feels just simpler and cleaner to me and it doesn't generate code so you would save some time.  For sure you would find a case where it’s better to use Dagger but it’s would be just a pity to not try Koin in some simpler projects, and check for yourself if you won’t smile once or twice. It’s fastly growing with new features so who knows if it won’t catch up with Dagger in near future.

Let's make the world better for everyone - join us
READ ALSO FROM Android
Read also
Need a successful project?
Estimate project or contact us