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 theGlobalContext
. startKoin
describe a Koin container instance and register it in KoinGlobalContext
. 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 definitionsingle { //definition }
– provide a singleton bean definition (also aliased asbean
)get()
– resolve a component dependency (also can use name, scope or parameters)bind()
– add type to bind for given bean definitionbinds()
– add types array for given bean definitionscope { // scope group }
– define a logical group forscoped
definitionscoped { //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 single
function 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 containerget()
– eager fetch instance from Koin container- Getting a ViewModel instance for a given class can be done directly with
getViewModel
andby 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 currentScope
property (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.