Koin 101

Uğur Çakmak
MindOrks

--

Koin is a dependency injection framework for Kotlin and Koin oversimplify to manage dependencies, unlike Dagger. Truly Koin is very simple to understand. I remembered that I had tried so much to understand that how Dagger works but I’ve not been same experience with Koin. Let’s take a look to official meaning of Koin on its website.

“ A pragmatic lightweight dependency injection framework for Kotlin developers. Written in pure Kotlin using functional resolution only: no proxy, no code generation, no reflection!”.

Before we deep into the Koin, we add its dependency

implementation 'org.koin:koin-android:$koin_version'

Let’s have a quick look at the keywords of Koin which is most using:

  • module { } - as you can guess, it creates a module or submodule.
  • factory { } - Creates instance of given class.

Note: factory create an instance every time it’s called.

  • single { } - Creates instance of given class like factory but it creates as singleton object
  • get() - official document says “Resolve a component dependency” I told myself “Ok, fine but what does it mean?”

We suppose we have following classes:

interface Presenter

class MyPresenter(val repository : Repository) : Presenter

class DataRepository()

As you can see, MyPresenter class implements the Presenter interface and take a Repository instance as a parameter. If we want to create an instance of MyPresenter class, we should have a class which is implemented the Repository interface. So far so good, we suppose we have a DataRepository class which is implemented the Repository. So how can we pass the DataRepository to MyPresenter class as an argument? We will see it!

First things first, we starting declare a module. And then, we are going to define DataRepository.

val myPresenterModule = module {

single { DataRepository() }
}

At above, we defined module and created instance of DataRepository as singleton. You remember single keyword create singleton objects. Next we are going to define MyPresenter but there is a thing which we should pay attention. MyPresenter class need a class which is implemented the Repository.

val myPresenterModule = module {

single { DataRepository() }

factory<Presenter> { MyPresenter( get() ) }
}

Here we used get() and what does it do? The get method finds a suitable object which the constructor what it need. The object mentioned here is the DataRepository.

We’ve used the “factory” as factory<T> {T}. What is this meaning? If we use the factory following;

val myPresenterModule = module {

factory{ MyPresenter( get() ) }
}

The type of this definition is the given type of class so MyPresenter. If we want to be sure what type is, we should use following version.

val myPresenterModule = module {

factory<Presenter> { MyPresenter( get() ) }
}

This version can be used factory{ MyPresenter(get()) as Presenter} but usually it is used like above.

And we can initialize the Koin in our application class like that.

start Koin

When it comes to the Injection? It is very easy.

injection lazy

Or we can inject directly, not lazy.

injection directly

What About ViewModel

As always we start by adding the dependency.

implementation "org.koin:koin-android-viewmodel:$koin_version"

It is easy inject a ViewModel as well. This time we going to use another useful Koin keyword. viewModel Let’s update the our myPresenterModule module

val myPresenterModule = module {

single { DataRepository() }

factory<Presenter> { MyPresenter( get() ) }
viewModel { MyViewModel( get() ) }}

And next, we can inject the MyViewModel to our class easily

And Scope?

Defining the scope we can make the decision that how long lifecycle of injected object. Let’s say we have an object depends the activity’s life-cycle which will have been injected. How would we implement it? This is where scope comes into play. Of course, first things first, we add the dependency.

implementation "org.koin:koin-android-scope:$koin_version"

Then next we can define the scope.

val myPresenterModule = module {

scope("myPresenter") { MyPresenter() }
}

After that we should bind the Scope to the Activity and we should bind into the onCreate method.

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

val scope = getKoin().getOrCreateScope("myPresenter")
bindScope(scope)}

As you see above that we’ve used getOrCreateScopemethod. Koin has three methods about the Scope.

  • createScope(id : String) - creates a scope with it is given id in the Koin scope registry
  • getScope(id : String) - retrieves a previously created scope with given id, from the Koin scope registry
  • getOrCreateScope(id : String) - creates or retrieves if already created, the scope with given id in the Koin scope registry.

And if we want to close the scope,

scope.close()

Notice that if you close the scope, you can’t inject from the instance which is closed

Naming a definition

As default, the Koin gives a name for our classes. Let’s take an example

val myPresenterModule = module {

single { DataRepository() }
}

Koin will give name “DataRepository” for the DataRepository by default. But if we want to inject two same type objects we have to give a name to them.

val myPresenterModule = module {
factory<Presenter>("release") { MyPresenter( get() ) }
factory<Presenter>("debug") { MyPresenter( get() ) }
}

And if we want to inject one of them, we have to use name.

val service : Service by inject(name = "debug")

This is the end of the article. We’ve taken a quick look at the Koin. You can look at the other topics which I haven’t mentioned such as ‘properties’, through the official website. Happy coding.

--

--