Koin - makstron/info GitHub Wiki

It is Service Locator, not a DI. But can be used as a DI.

Koin home page

Current Version

// latest version
koin_version = '3.1.2'

Core features

// Koin for Kotlin
implementation "org.koin:koin-core:$koin_version"
// Koin extended & experimental features
implementation "org.koin:koin-core-ext:$koin_version"
// Koin for Unit tests
testImplementation "org.koin:koin-test:$koin_version"
// Koin for Java developers is now part of core
// implementation "org.koin:koin-java:$koin_version"

Android

// Koin for Android
implementation "org.koin:koin-android:$koin_version"
// Koin Android Scope features
implementation "org.koin:koin-android-scope:$koin_version"
// Koin Android ViewModel features
implementation "org.koin:koin-android-viewmodel:$koin_version"
// Koin Android Experimental features
implementation "org.koin:koin-android-ext:$koin_version"

AndroidX

// Koin AndroidX Scope features
implementation "org.koin:koin-androidx-scope:$koin_version"
// Koin AndroidX ViewModel features
implementation "org.koin:koin-androidx-viewmodel:$koin_version"
// Koin AndroidX Fragment features
implementation "org.koin:koin-androidx-fragment:$koin_version"
// Koin AndroidX WorkManager
implementation "org.koin:koin-androidx-workmanager:$koin_version"
// Koin AndroidX Jetpack Compose
implementation "org.koin:koin-androidx-compose:$koin_version"
// Koin AndroidX Experimental features
implementation "org.koin:koin-androidx-ext:$koin_version"

A KoinApplication instance is a Koin container instance configuration.

The startKoin function is the main entry point to launch Koin container. It need a list of Koin modules to run. Modules are loaded and definitions are ready to be resolved by the Koin container

To build a new KoinApplication, use the following functions:

  • koinApplication { } - create a KoinApplication container configuration
  • startKoin { } - create a KoinApplication container configuration and register it in the GlobalContext to allow the use of GlobalContext API

The GlobalContext is a default JVM context strategy for Koin. It's called by startKoin and register to GlobalContext. This will allow us to register a different kind of context, in the perspective of Koin Multiplatform.

For initiate Koin in a Global scope

// start a KoinApplication in Global context
class App : Application() {

    override fun onCreate() {
        super.onCreate()
        startKoin {
            // use Android logger - Level.INFO by default
            androidLogger(Level.DEBUG)
        
            //inject Android context
            androidContext(this@DbMakerApplication)
            //or 
            androidContext(/* your android context */)
        
            // use properties from assets/koin.properties
            androidFileProperties()
        
            // declare used modules
            modules(viewModelsModule + useCasesModule + repositoriesModule + dataSourcesModule + dbModule + retrofitModule)
        }
    }
}

If we want to use an isolated Koin instance, you can just declare it like follow:

// create a KoinApplication
val myApp = koinApplication {
    // declare used modules
    modules(coffeeAppModule)
}

To configure your KoinApplication instance, you can use any of the following functions :

  • logger() - describe what level and Logger implementation to use (by default use the EmptyLogger)
  • modules() - set a list of Koin modules to load in the container (list or vararg list)
  • properties() - load HashMap properties into Koin container
  • fileProperties() - load properties from given file into Koin container
  • environmentProperties() - load properties from OS environment into Koin container

You can NOT call the startKoin function more than once.

But you can use directly the loadKoinModules() or unloadKoinModules() functions.

    loadKoinModules(module1,module2 ...)
    unloadKoinModules(module1,module2 ...)

You can close all the Koin resources and drop instances & definitions. For this you can use the stopKoin() function from anywhere, to stop the Koin GlobalContext. Else on a KoinApplication instance, just call close()

A Koin module is a "space" to gather Koin definition. It's declared with the module function.
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

Components doesn't have to be necessarily in the same module. A module is a logical space to help you organize your definitions, and can depend on definitions from other module. Definitions are lazy, and then are resolved only when a a component is requesting it.

Getting Android context inside a Module

The androidContext() & androidApplication() functions allows you to get the Context instance in a Koin module, to help you simply write expression that requires the Application instance.

val appModule = module {
    // create a Presenter instance with injection of R.string.mystring resources from Android
    factory {
        MyPresenter(androidContext().resources.getString(R.string.mystring))
    }
}

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

  • factory { //definition } - provide a factory bean definition. A factory component declaration is a definition that will gives you a new instance each time you ask for this definition (this instance is not retained by Koin container, as it won't inject this instance in other definitions later).
  • single { //definition } - provide a singleton bean definition (also aliased as bean), means that Koin container will keep a unique instance of your declared component.
  • get() - resolve a component dependency (also can use name, scope or parameters). This function is usually used into constructor, to inject constructor values.
  • 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
  • named() - function allow you to give a qualifier either by a string, an enum or a type. It is used to name your definitions.
  • by inject() - lazy evaluated instance from Koin container
class DbConnectionsVM(application: Application, var connectionsUseCase: DbConnectionsUseCaseI) : AndroidViewModel(application)
class FeedbackRepository(private val localDataSource: FeedbackDataSourceI) : FeedbackRepositoryI
class LocalFeedbackDataSource(val db: Db) : FeedbackDataSourceI
val someModule = module {
    viewModel {    DbConnectionsVM(get(), get())  }

    // declare single instance for DbManagerFactory
    single {    DbManagerFactory()  }

    single<DbConnectionsUseCaseI> {    DbConnectionsUseCase(get())  }

    //You can specify a name to your definition, to help you distinguish two definitions about the same type
    single<FeedbackDataSourceI>(named("local")) {    LocalFeedbackDataSource(get())  }
    single<FeedbackDataSourceI>(named("remote")) {    RemoteFeedbackDataSource(get())  }

    // Will match type FeedbackRepositoryI only
    single<FeedbackRepositoryI> {    FeedbackRepository(get(qualifier = named("local"))) }

    // Will match type Service only
    single { ServiceImp() as Service }

    // Will match types ServiceImp & Service
    single { ServiceImp() } bind Service::class

    single {    Db.getInstance()    }
}

Activity, Fragment & Service are extended with the KoinComponents extension. You gain access to: by inject(), get()

class DetailActivity : AppCompatActivity() {
    // Lazy injected Presenter instance
    val presenter : Presenter by inject()

    // Retrieve a UseCase instance
    val useCase : UseCase = get()

    //get your view model
    val vm: MyViewModel by viewModel()
   ...
}

If you need to inject() or get() an instance from another class, just tag it with KoinComponent interface.

Create instances at the start

A definition or a module can be flagged as CreatedAtStart, to be created at start (or when you want). First set the createOnStart flag on your module or on your definition.

single(createdAtStart=true) { TestServiceImp() } val myModuleB = module(createdAtStart=true) {

Dealing with generics

Koin definitions doesn't take in accounts generics type argument. To allow you, use the 2 definitions you will have to differentiate them via their name, or location (module).

module {
    single(named("Ints")) { ArrayList<Int>() }
    single(named("Strings")) { ArrayList<String>() }
}

Overriding definition or module

Koin won't allow you to redefinition an already existing definition (type,name,path ...)

module {
    single<Service> { ServiceImp() }
    single<Service> { TestServiceImp() }
}

To allow definition overriding, you have to use the override parameter

// Allow override for all definitions from module
module(override#true) {
    single<Service> { ServiceImp() }

    // override for this definition
    single<Service>(override#true) { TestServiceImp() }
}

Koin can handle properties from environment or external property file, to help you inject values into your definitions.

In a Koin module, you can get a property by its key:

    // Key - value
    server_url=http://service_url

Scope is a fixed duration of time or method calls in which an object exists. Another way to look at this is to think of scope as the amount of time an object’s state persists. When the scope context ends, any objects bound under that scope cannot be injected again (they are dropped from the container).

By default in Koin, we have 3 kind of scopes:

  • single - definition, create an object that persistent with the entire container lifetime (can't be dropped).
  • factory - definition, create a new object each time. Short live. No persistence in the container (can't be shared).
  • scoped - definition, create an object that persistent tied to the associated scope lifetime.

Components related to the UI parts must be released on soon as we don't need them anymore.

  • long live components (Services, Data Repository ...) - used by several screens, never dropped

Long live components can be easily described as single definitions.

  • medium live components (user sessions ...) - used by several screens, must be dropped after an amount of time
  • short live components (views) - used by only one screen & must be dropped at the end of the screen

To declare a scoped definition, use the scoped function like follow. A scope gathers scoped definitions as a logical unit of time.

module {
    scope<MyType>{
        scoped { Presenter() }
        // ...
    }
}

A Koin Scope is defined by its:

  • scope name - scope's qualifier
  • scope id - unique identifier of the scope instance

From a Koin instance, you can access:

  • createScope(id : ScopeID, scopeName : Qualifier) - create a closed scope instance with given id and scopeName
  • getScope(id : ScopeID) - retrieve a previously created scope with given id
  • getOrCreateScope(id : ScopeID, scopeName : Qualifier) - create or retrieve if already created, the closed scope instance with given id and scopeName

https://medium.com/@sreeharikv112/dependency-injection-using-koin-4de4a3494cbe

https://proandroiddev.com/understanding-android-scopes-with-koin-cfe6b60ca579

⚠️ **GitHub.com Fallback** ⚠️