What’s new in Koin?

Features and migration snippets to help you update your code with new release of Koin

How to update your code to Koin 0.7.1

This new 0.7.x was focused of how to bring more concisness and simplicity in our way of writing dependency injection with Koin. Start process, DSL, modules has then been reviewed in this idea. Android module has been simplified for a better focus. We have dropped the ContextAware components due to dependency the support library and because those components where not so easy to use or well understand. Whereas it’s simple to use factory definition or just release context in the given component. Below you will find the ContextAware component source code, to help migrate your code.

This new version also brings some community contribution : thread safety for instance resolution, allowing Koin to be used in heavy multithread / coroutines applications.

DSL & Modules update

Since the beginning, the Koin modules are written against classes. In 0.6.x and prior version, we would write a module like:

class WeatherModule : Module() {
    override fun context() = applicationContext {
        // WeatherActivity context
        context(name = "WeatherActivity") {
            // Provide a factory for presenter WeatherContract.Presenter
            provide { WeatherPresenter(get()) }
        }
        
        // Weather data repository
        provide { WeatherRepository() }
    }
}

for given classes:

class WeatherPresenter(val weatherRepository: WeatherRepository)
class WeatherRepository()

Today we write it directly with the applicationContext function:

val weatherModule  = applicationContext {
        // WeatherActivity context
        context(name = "WeatherActivity") {
            // Provide a factory for presenter WeatherContract.Presenter
            provide { WeatherPresenter(get()) }
        }
        
        // Weather data repository
        provide { WeatherRepository() }
    }

If you had any properties or other components in your previous Module class, you can write it directly into your Kotlin file (no need of Class to handle it).

Android module

The AndroidModule class has been dropped. We don’t need it anymore. Just use the applicationContext function to declare your module, like above.

an AndroidModule defintion:

class WeatherModule : AndroidModule() {
    override fun context() = applicationContext {
        // WeatherActivity context
        context(name = "WeatherActivity") {
            // Provide a factory for presenter WeatherContract.Presenter
            provide { WeatherPresenter(get()) }
        }
        
        // Weather data repository
        provide { WeatherRepository(androidApplication) }
    }
}
class WeatherPresenter(val weatherRepository: WeatherRepository)
class WeatherRepository(val application : Application)

can be written now:

val weatherModule = applicationContext {
        // WeatherActivity context
        context(name = "WeatherActivity") {
            // Provide a factory for presenter WeatherContract.Presenter
            provide { WeatherPresenter(get()) }
        }
        
        // Weather data repository
        provide { WeatherRepository(androidApplication()) }
        // or
        // provide { WeatherRepository(get()) }
    }

Starter functions

The startKoin() function has been reviewed to be more opened to extension, and to accept the new module definition. You still write your Koin start like:

startKoin(listOf(weatherModule))

or in Android:

startKoin(this, listOf(weatherModule))

Easier interface binding

To bind an interface with a bean definition, consider write it with provide and bind if you want your bean to be resolved against its interface and implementation. Else, just us the as cast Kotlin operator directly in your definition:

val weatherModule : Module = applicationContext {
        // Allow resolve against LocalDataSource & WeatherDatasource types
        provide { LocalDataSource() } bind WeatherDatasource::class
    }
val weatherModule : Module = applicationContext {
        // Allow resolve against WeatherDatasource type only
        provide { LocalDataSource() as WeatherDatasource }
    }

Bean provider Aliases

This new version brings some provide aliases:

Those aliases allow to use the name attribute if needed. But can’t be used with the bind keyword. Those aliases are intended to be used as shorcut declaration, and provide allow a complete bean definition. Then:

val weatherModule : Module = applicationContext {
        bean { MyComponent() as Interface }
        // equivalent to provide { MyComponent() as Interface }

        factory { OtherComponent() as Interface }
        // equivalent to provide(isSingleton=false) { OtherComponent() as Interface }
    }

But it won’t be possible to write additional binding with bind. You will have to use provide:

val weatherModule : Module = applicationContext {
        provide { MyComponent() } bind Interface::class

        provide(isSingleton=false) { OtherComponent() } bind Interface::class
    }

Bean definition override

If you provide several bean definition with same name or type, the last definition will override the original definition:

val weatherModule = applicationContext {
        // Weather data repository
        provide { WeatherRepositoryImpl() as WeatherRepository}
    }
val otherWeatherModule = applicationContext {
        // Weather data repository
        provide { OtherWeatherRepositoryImpl() as WeatherRepository}
    }

if your start your app with startKoin(listOf(weatherModule, otherWeatherModule)), you will be able to resolve the WeatherRepository type definition. But it will be against the OtherWeatherRepositoryImpl implmentation.

To provide several bindings of the same type, use the name attribute, and resolve it by ots name:

val weatherModule = applicationContext {
        // Weather data repository
        provide("default") { WeatherRepositoryImpl() as WeatherRepository}
    }
val otherWeatherModule = applicationContext {
        // Weather data repository
        provide("other") { OtherWeatherRepositoryImpl() as WeatherRepository}
    }

Context Isolation

Context isoliation (defintions are visible between parent/child of sub contexts), is now inactive by default.

To make it active, just set it before starting Koin: Koin.useContextIsolation = true

Default Logger & New logging display

No need to setup your Koin logger anymore. By default, Koin.logger will be set to PrintLogger() with Kotlin apps, and to AndroidLogger() for Android apps. New logging display allow more easy debugging and understanding of how instance are resolved and created:

(KOIN) :: [Property] test.koin >> 'done'
(KOIN) :: [Property] os.version >> 'weird'
(KOIN) :: [init] loaded 2 properties from 'koin.properties' file
(KOIN) :: [init] declare : Bean[name='',class=org.koin.test.standalone.MVPArchitectureTest.Repository,singleton=true,binds~()]
(KOIN) :: [Scope] create [View] with parent [ROOT]
(KOIN) :: [init] declare : Bean[name='',class=org.koin.test.standalone.MVPArchitectureTest.View,singleton=true,binds~()]
(KOIN) :: [init] declare : Bean[name='',class=org.koin.test.standalone.MVPArchitectureTest.Presenter,singleton=true,binds~()]
(KOIN) :: [init] declare : Bean[name='',class=org.koin.test.standalone.MVPArchitectureTest.DebugDatasource,singleton=true,binds~(org.koin.test.standalone.MVPArchitectureTest.Datasource)]
(KOIN) :: [init] loaded 4 definitions
(KOIN) :: Resolve [org.koin.test.standalone.MVPArchitectureTest.View] ~ Bean[name='',class=org.koin.test.standalone.MVPArchitectureTest.View,singleton=true,binds~()]
(KOIN) :: (*) Created
(KOIN) :: Resolve [org.koin.test.standalone.MVPArchitectureTest.Presenter] ~ Bean[name='',class=org.koin.test.standalone.MVPArchitectureTest.Presenter,singleton=true,binds~()]
(KOIN) :: 	Resolve [org.koin.test.standalone.MVPArchitectureTest.Repository] ~ Bean[name='',class=org.koin.test.standalone.MVPArchitectureTest.Repository,singleton=true,binds~()]
(KOIN) :: 		Resolve [org.koin.test.standalone.MVPArchitectureTest.Datasource] ~ Bean[name='',class=org.koin.test.standalone.MVPArchitectureTest.DebugDatasource,singleton=true,binds~(org.koin.test.standalone.MVPArchitectureTest.Datasource)]
(KOIN) :: 		(*) Created
(KOIN) :: 	(*) Created
(KOIN) :: (*) Created
(KOIN) :: Resolve [org.koin.test.standalone.MVPArchitectureTest.Repository] ~ Bean[name='',class=org.koin.test.standalone.MVPArchitectureTest.Repository,singleton=true,binds~()]
(KOIN) :: Resolve [org.koin.test.standalone.MVPArchitectureTest.DebugDatasource] ~ Bean[name='',class=org.koin.test.standalone.MVPArchitectureTest.DebugDatasource,singleton=true,binds~(org.koin.test.standalone.MVPArchitectureTest.Datasource)]
(KOIN) :: Resolve [org.koin.test.standalone.MVPArchitectureTest.Presenter] ~ Bean[name='',class=org.koin.test.standalone.MVPArchitectureTest.Presenter,singleton=true,binds~()]
(KOIN) :: Release context : View

Android ContextAware Components

We have dropped the ContextAware components, to let people choose their own solution to automate context dropping. It’s always tedious to ask/force people to use an abstract component, between you and your Android component hierarchy. It also give us a dependency with Android support library, only for those components.

How to replace your ContextAware component?

val weatherModule = applicationContext {
        context(name = "WeatherActivity") {
            provide { WeatherPresenter( }
        }
    }

class WeatherActivity : ContextAwareActivity(), WeatherContract.View {
    
    // associated context name
    override val contextName = "WeatherActivity"

    val presenter by inject<WeatherPresenter>()
}

Use the releaseContext()

Use the releaseContext() function to release your context when needed:

class WeatherActivity : AppCompatActivity(), WeatherContract.View {
    
    val presenter by inject<WeatherPresenter>()

    override fun onPause(){
        super.onPause()
        // drop your context
        releaseContext("WeatherActivity")
    }

    //or onStop if your prefer
}

Use the factory provider

Use the factory bean provider, that give a new instance each time you ask the definition. But each instance will be dropped after use:

val weatherModule = applicationContext {
        //no need of particular context
        factory { WeatherPresenter() }
    }

class WeatherActivity : AppCompatActivity(), WeatherContract.View {
    // will be dropped when WeatherActivity will be dropped
    val presenter by inject<WeatherPresenter>()
}

Thus, no need to create sub-context. Factory instances are *freed* once used.