Koin 2.0 For Android

What is Koin?

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

Koin is a DSL, a lightweight container and a pragmatic API.

Why Koin?

Earlier, we were using dagger 2 for dependency injection on our jobseeker android app. Dagger 2 is not a very kotlin way.

So, in our next android app, we migrated to Koin, a pragmatic lightweight dependency injection framework written in pure Kotlin and get a feel for what Koin is all about.

Koin 2.0 vs Dagger 2

  • No need to create a class for your module. Instead, use the top level feature of Kotlin i.e define all modules in single class and add them in application call as list of modules. Koin doesn’t require you to create a proper interface to define your component like Dagger 2.
  • Koin doesn’t use the annotation processing, you don’t have to use the kapt compiler plugin and worry about it.

Set up

Add following dependencies to build.gradle(app):

// Add Jcenter to your repositories if needed
repositories {
jcenter()
}
dependencies {
// Koin for Android
implementation 'org.koin:koin-androidx-viewmodel:2.0.1'
implementation 'org.koin:koin-androidx-scope:2.0.1'
}

Note-: refer to https://github.com/adiamartya/KoinDemo for better understanding of codes & examples

Startup DSL

  • Starting Koin means run a KoinApplication instance into the GlobalContext.
  • startKoin describe a Koin container instance and register it in Koin GlobalContext . Define and declare all loggers, context and modules here.
  • For Android, don’t forget to declare your AndroidContext with the androidContext function:
/**
* start koin for dependency injection
*/
startKoin
{
//use Koin Android Logger
androidLogger()

//declare application context
androidContext(this@KoinDemoApplication)

//declare all modules here
modules(listOf(appModule, viewModelModule, repoModule, ...))
}

Performance

Rafa Vázquez has written a benchmark project to help measure some performances for DI frameworks. This benchmark runs a project of 400 definitions.

With Koin 2.0 -:

Module DSL

A Koin module gather definitions that you will inject/combine for your application. To create a new module, just use the following function:

  • module { // module content } – create a Koin Module

To describe your content in a module, you can use the following functions:

  • factory { //definition } – provide a factory bean definition
  • single { //definition } – provide a singleton bean definition (also aliased as bean)
  • get() – resolve a component dependency (also can use name, scope or parameters)
  • bind() – add type to bind for given bean definition
  • binds() – add types array for given bean definition
  • scope { // scope group } – define a logical group for scoped definition
  • scoped { //definition }– provide a bean definition that will exists only in a scope

Note: the named() function allow you to give a qualifier either by a string or a type. It is used to name your definitions.

Defining a module

Use module function to declare a koin module. Declare all your components inside modules.

val myModule = module {
// declare components
}

Defining a singleton

Declaring a singleton component means that Koin container will keep a unique instance of your declared component. Use the singlefunction in a module to declare a singleton:

val appModule = module {
single {
//define singleton component eg-:Room database instance
Room.databaseBuilder(get(),KoinDatabase::class.java,"koin_db").build()
}
}

Defining a factory

factory component declaration will give a new instance each time you ask for definition.

class KoinRepo(koinDatabase: KoinDatabase) { }

/**
* Factory modules - each time new instance
*/

val repoModule = module {
factory { KoinRepo(get()) }
}

ViewModel DSL

The koin-android-viewmodel introduces a new viewModel DSL keyword that comes in complement of single and factory, to help declare a ViewModel component and bind it to an Android Component lifecycle.

class KoinViewModel(private val koinRepo: KoinRepo) : ViewModel() {}

val viewModelModule = module {
viewModel
{ KoinViewModel(get()) }
}

Naming components

You can specify a name to your definition, to help you distinguish two definitions about the same type.
While fetching, just request your definition with its name.

There are two ways to name a component-:

Use string qualifier :

val viewModelModule = module { 
viewModel(named("vm")) { KoinViewModel(get()) }
}

Use type qualifier. For eg a class name as type qualifier:

val viewModelModule = module { 
viewModel(named<KoinScopeActivity>()) { KoinViewModel(get()) }
}

Get components

get() and by inject() functions let you specify a definition name if needed. This name is a qualifier produced by the named() function.

Retrieving definitions

  • by inject() – lazy evaluated instance from Koin container
  • get() – eager fetch instance from Koin container
  • Getting a ViewModel instance for a given class can be done directly with getViewModel and by viewModel

Lazy approach

//fetching KoinViewModel instance 
val viewModel: KoinViewModel by viewModel()
or
val viewModel: KoinViewModel by inject()

//fetching KoinViewModel instance by string qualifier
val viewModel: KoinViewModel by viewModel(named("vm"))
or
val viewModel: KoinViewModel by inject(named("vm") )

//fetching KoinViewModel instance by type qualifier
val viewModel: KoinViewModel by viewModel(named<KoinScopeActivity>())
or
val viewModel: KoinViewModel by inject(named<KoinScopeActivity>())

Eager approach

getViewModel<KoinViewModel>() 
or
get<KoinViewModel>()

getViewModel<KoinViewModel>(named("vm"))
or
get<KoinViewModel>(named("vm"))

getViewModel<KoinViewModel>(named<KoinScopeActivity>())
or
get<KoinViewModel>(named<KoinScopeActivity>())

Scope

A scope is a context with a fixed duration of time, in which an object exists. When the scope ends, any objects bound under that scope cannot be injected again.

You cannot recreate same scope unless that scope has been closed or destroyed.

Declaring scope

scope(named("customScope")) {
scoped { KoinRepo(get()) }
//Multiple ways to declare a component. Here, i am declaring KoinViewModel
viewModel { KoinViewModel(get()) }
viewModel(named<KoinScopeActivity>()) { KoinViewModel(get()) }
viewModel(named("vm")) { KoinViewModel(get()) }
}

In the DSL, a scope define a set of definitions. A scoped definition is a temporary single definition that is inside a scope. To use a scope, we create an instance of a scope. A factory definition is allowed inside a scope, letting it resolve dependencies from the current scope and returning a new instance each time.

Creating Scope

val scope:Scope = getKoin().createScope("id", named("customScope"))
val viewModel = scope.get<KoinViewModel>(named<KoinScopeActivity>())

scope.close()//----------> close scope when no more in use. If you are using activity scope then it will be closed automatically when activity is destroyed

Activity/Fragment Scope

You can easily declare a scope tied to your view, and easily retrieve anything from it with the currentScopeproperty (Koin’s extension for LifecycleOwner components):

The currentScope property is created when you access it and follow the lifecycle of your Android component. It is destroyed when receiving theON_DESTROY signal.

val viewModelModule = module {
//declare a scope for KoinFragmentActivity
scope(named<KoinFragmentActivity>()) {
scoped { UserDetailsRepo(get()) }
viewModel(named<KoinFragment>()) { UserDetailsViewModel(get()) }
}
}


Get UserDetailsViewModel in KoinFragmentActivity-:

// lazy way
val viewModel: UserDetailsViewModel by currentScope.viewModel(this, named<KoinFragment>())

//Eager way
currentScope.getViewModel<UserDetailsViewModel>(this, named<KoinFragment>())


Get UserDetailsViewModel in KoinFragment(KoinFragmentActivity is the parent activity) -:

activity?.currentScope?.getViewModel<UserDetailsViewModel>(this, named<KoinFragment>())

Shared ViewModel

One ViewModel instance can be shared between Fragments and their host Activity.

To inject a shared ViewModel in a Fragment use:

  • by sharedViewModel() – lazy delegate property to inject shared ViewModel instance into a property.
  • getSharedViewModel() – directly get the shared ViewModel instance.

Declare the ViewModel only once:

val viewModelModule = module {
viewModel
{ KoinViewModel(get()) } //----->viewmodel without qualifier
}

//Get viewmodel in fragment
getSharedViewModel<UserDetailsViewModel>()
NOTE: To get same instance of viewmodel that has been used in activity, don't use custom scope or currentScope or getViewModel. This all will give new instances of viewmodel.

Please refer to https://github.com/adiamartya/KoinDemo
All the examples have been quoted from this project.