Kodein-DI on Android

You can use Kodein-DI as-is in your Android project or use the util library kodein-di-android.

Kodein-DI does work on Android as-is. The kodein-di-android-* extensions add multiple android-specific utilities to Kodein-DI.
Using or not using this extension really depends on your needs.
Starting from 7.3, using kodein-di-android-* extensions in your Android projects needs that you support Android 21 as the minimum SDK. Using Kodein-DI as-is does not force with minimum SDK requirement.
If you use Jetpack Compose you can use our specific module kodein-di-framework-compose or kodein-di-framework-android-x-compose with more helpers.

Have a look at the Android demo project!

Install

How to use kodein-di-android:
  1. Add this line in your dependencies block in your application build.gradle file:

    implementation 'org.kodein.di:kodein-di-framework-android-???:7.21.1'

    Kodein-DI offers support for:

    Barebone Android

    kodein-di-framework-android-core

    Android + Support library

    kodein-di-framework-android-support

    Android + AndroidX library

    kodein-di-framework-android-x

    Android + AndroidX ViewModel

    kodein-di-framework-android-x-viewmodel

    Android + AndroidX ViewModel with SavedStateHandle

    kodein-di-framework-android-x-viewmodel-savedstate

    From 6.3.0 On the JVM, you must be targeting JDK 1.8 minimum!
    If you are using SupportFragment in your application, then you must use either the -support or the -x package.
  2. Declare the dependency bindings in the Android Application, having it implements DIAware.

    Example: an Android Application class that implements DIAware
    class MyApp : Application(), DIAware {
    	override val di by DI.lazy { (1)
    	    /* bindings */
    	}
    }
    1 Using DI.lazy allows you to access the Context at binding time.
    Don’t forget to declare the Application in the AndroidManifest.xml file!
  3. In your Activities, Fragments, and other context aware android classes, retrieve the DI object with the di function.

  4. Retrieve your dependencies!

Retrieving

Injection does not work on Android classes that are reified by the system (Activity, Fragment, etc.) due the fact that…​ they are reified by the system! Therefore, on such classes, you can either use retrieval, or if you want these classes to be independent of Kodein-DI, use the dependency holder pattern.

Getting a DI object

You can always get the DI object by using:

  • di() inside an Android class (such as Context, Activity, Fragment, etc.)

  • di(context) or di { context } inside another class

The di function will only work if your Android Application class implements the DIAware interface.
The di result should be cached and not used multiple times in a row.

Being DIAware

It is very simple to have your Android classes be DIAware:

Example: a DIAware Android Activity
class MyActivity : Activity(), DIAware {

    override val di by di() (1)

    val ds: DataSource by instance()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ds.connect() (2)
        /* ... */
    }

}
1 Retrieving the application DI instance via context.
2 Because everything is lazy, the di AND ds instances will both be retrieved only when needed, which is at that time.

Using a Trigger

If you want all dependencies to be retrieved at onCreate, you can very easily use a trigger:

Example: using an trigger in a DIAware Android Activity
class MyActivity : Activity(), DIAware {

    override val di by di()

    override val diTrigger = DITrigger() (1)

    val ds: DataSource by instance()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        diTrigger.trigger() (2)
        /* ... */
    }

}
1 Just create a trigger, and Kodein-DI will automatically use it.
2 The di AND all dependencies will both be retrieved at that time.
Using this approach has an important advantage: as all dependencies are retrieved in onCreate, you can be sure that all your dependencies have correctly been retrieved, meaning that there were no non-declared dependency.
If you only use instance (no provider or factory), you can also be sure that there were no dependency loop.

View Models

Kodein-DI presents several options for dependency injection and/or retrieval for View Models.

Injection and retrieval of plain View Models

The kodein-di-framework-android-x-viewmodel module simplifies injection and retrieval of View Models which inherit androidx.lifecycle.ViewModel, without requiring that the View Model implement DIAware.

If your View Model uses a SavedStateHandle, use the View Model with SavedStateHandle module instead.

Use a Provider binding for plain ViewModel instances.

Example: Create a binding for a ViewModel
val di = DI {
    bindProvider { MyViewModel(
        myDependency = instance()
    ) }
}
Example: Retrieve a plain ViewModel in an Activity or Fragment
class MyActivity : AppCompatActivity(), DIAware {
    override val di: DI by closestDI()
    private val viewModel: MyViewModel by viewModel()
}

class MyFragment : Fragment(), DIAware {
    override val di: DI by closestDI()
    private val viewModel: MyViewModel by viewModel()
}
The viewModel delegate for Fragments will use its own view model store by default to obtain the ViewModel instance, delegating to your Kodein-DI provider if the instance is not already available in the store. Use the ownerProducer argument if you need to use a different view model store, such as a parent fragment’s.

Injection and retrieval of View Models with SavedStateHandle

The kodein-di-framework-android-x-viewmodel-savedstate module simplifies injection and retrieval of View Models which inherit ViewModel and use a SavedStateHandle to persist state.

Use a Factory binding for ViewModel instances which use a SavedStateHandle argument.

Example: Create a binding for a View Model with a SavedStateHandle argument
val di = DI {
    bindFactory { savedStateHandle: SavedStateHandle -> MyViewModel(
        savedStateHandle = savedStateHandle,
        myDependency = instance()
    ) }
}
Example: Retrieve a ViewModel with a SavedStateHandle in an Activity or Fragment
class MyActivity : AppCompatActivity(), DIAware {
    override val di: DI by closestDI()
    private val viewModel: MyViewModel by viewModelWithSavedStateHandle()
}

class MyFragment : Fragment(), DIAWare {
    override val di: DI by closestDI()
    private val viewModel: MyViewModel by viewModelWithSavedStateHandle()
}

Retrieval inside View Models

To use Kodein-DI retrieval inside View Models, you need an Android context. For that, View Models need to implement AndroidViewModel.

It is very easy to use Kodein-DI inside View Models:

Example: using a trigger in a DIAware Android Activity
class MyViewModel(app: Application) : AndroidViewModel(app), DIAware {

    override val di by di() (1)

    val repository : Repository by instance()
}
1 Retrieving the application’s DI container.

View Model Provider

View Model Factory

In case the delegates for plain View Models and SavedStateHandle extension don’t satisfy your needs, or if you want your View Models to be independent of Kodein-DI, then you need to inject them (meaning passing their dependencies by constructor). To do that, you need to create your own ViewModelProvider.Factory.

Here is a simple one:

A DI View Model Factory
class DIViewModelFactory(val di: DI) : ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T =
            di.direct.Instance(erased(modelClass))
}

Android module

Kodein-DI-Android proposes a Module that enables easy retrieval of a lot of standard android services.

This module is absolutely optional, you are free to use it or leave it ;).
Example: importing the android module
class MyApplication : Application(), DIAware {
    override val di by DI.lazy {
        import(androidXModule(this@MyApplication)) (1)
	    /* bindings */
    }
}
1 Can either be androidXModule or androidSupportModule or androidCoreModule.

You can see everything that this module proposes in the Kodein-Android module.kt file.

Example: using kodein-DI to retrieve a LayoutInflater
class MyActivity : Activity(), DIAware {
    override val di by di()
    val inflater: LayoutInflater by instance() (1)
}

If you are retrieving these classes inside a non-Android class, you need to define an Android Context as a DI context:

Example: using DI with a context to retrieve a LayoutInflater
val inflater: LayoutInflater by di.on(getActivity()).instance()

or

Example: using DI with a class context to retrieve a LayoutInflater
class MyUtility(androidContext: Context) : DIAware {

    override val di by androidContext.di()

    override val kodeinContext = diContext(androidContext) (1)

    val inflater: LayoutInflater by instance()
}
1 Defining the default context: the Android context to use to retrieve Android system services.

Android context translators

The android module provides a number of context translators. For example, they allow you to retrieve an activity scoped singleton inside a fragment, without manually specifying the activity.

The android modules automatically register these translators.

However, if you don’t want to use the android modules, but still need these translators, you can register them easily:

Example: importing the android module
class MyApplication : Application(), DIAware {
    override val di by DI.lazy {
        import(androidXContextTranslators) (1)
	    /* bindings */
    }
}
1 Can either be androidXContextTranslators or androidSupportContextTranslators or androidCoreContextTranslators.

Android scopes

Component scopes

Kodein-DI provides a standard scope for any component (Android or not). The WeakContextScope will keep singleton and multiton instances as long as the context (= component) lives.

Example: using an Activity scope
val di = DI {
    bind<Controller> { scoped(WeakContextScope.of<Activity>()).singleton { ControllerImpl(context) } } (1)
}
1 context is of type Activity because we are using the WeakContextScope.of<Activity>().
WeakContextScope is NOT compatible with ScopeCloseable.

Activity retained scope

Kodein-DI-Android provides the ActivityRetainedScope, which is a scope that allows activity-scoped singletons or multitons that are independent from the activity restart.

This means that for the same activity, you’ll get the same instance, even if the activity restarts.

This means that you should never retain the activity passed at creation because it may have been restarted and not valid anymore!
Example: using an Activity retained scope
val di = DI {
    bind<Controller> { scoped(ActivityRetainedScope).singleton { ControllerImpl() } }
}
This scope IS compatible with ScopeCloseable: see documentation.

Lifecycle scope

Kodein-DI-Android provides the AndroidLifecycleScope, which is a scope that allows activity-scoped singletons or multitons that are bound to a component lifecycle. It uses Android support Lifecycle, so you need to use Android support’s LifecycleOwner components.

Example: using an Activity retained scope
val di = DI {
    bind<Controller> { scoped(AndroidLifecycleScope<Fragment>()).singleton { ControllerImpl(context) } }
}
These lifecycles are NOT immune to activity restart due to configuration change.
This scope IS compatible with ScopeCloseable: see documentation.

Layered dependencies

The closest DI pattern

Android components can be thought as layers. For example, a View defines a layer, on top of an Activity layer, itself on top of the Application layer.

The di function will always return the DI container of the closest parent layer. In a View or a Fragment, for example, it will return the containing Activity’s DI container, if it defines one, else it will return the "global" Application DI container.

In the following code example, if MyActivity contains Fragments, and that these fragments get their DI object via di(), they will receive the MyActivity DI object, instead of the Application one.

Component based sub DI

In Android, each component has its own lifecycle, much like a "mini application". You may need to have dependencies that are defined only inside a specific component and its subcomponents (such as an activity). Kodein-DI allows you to create a DI instance that lives only inside one of your components:

Example: defining an Activity specific DI container
class MyActivity : Activity(), DIAware {

    override val di by subDI(di()) { (1)
        /* activity specific bindings */
    }

}
1 Creating a sub DI container that is valid for this activity and all components of this activity.

By default all bindings are cached. You can also define the way the parent DI container is extended by defining the copy mode. In below example each instance of activity will store a copy of the DI module (all bindings including singleton will be recreated per activity instance).

Example: defining an Activity specific DI container that copies all parent bindings
override val di by subDI(di(), copy = Copy.All) {
    /* component specific bindings */
}

Activity retained sub DI container

Kodein-DI-Android provides retainedSubDI for Activities. It creates a DI object that is immune to activity restarts.

This means that you should never access the containing activity it may have been restarted and not valid anymore!
Example: defining an Activity specific DI container
class MyActivity : Activity(), DIAware {

    override val di: DI by retainedSubDI(di()) { (1)
        /* activity specific bindings */
    }

}
1 Using retainedSubDI instead of subDI ensures that the DI object is retained and not recreated between activity restarts.

You can define the way the parent DI container is extended by defining the copy mode:

Example: defining an Activity specific DI container that copies all parent bindings
override val di by retainedSubDI(di(), copy = Copy.All) {
    /* component specific bindings */
}

Independent Activity retained DI container

Kodein-DI provides the retainedDI function that creates a DI instance that is independendant from the parent.

This means that all bindings in the application context are NOT available through this new DI container.
Example: defining an independent DI Container.
class MyActivity : Activity() {

    val activityKodein: DI by retainedDI { (1)
        /* activity specific bindings */
    }

}

Kodein-DI in Android without the extension

Being DIAware

It is quite easy to have your Android components being DIAware (provided that your Application class is DIAware).

Using lazy

Example: a DIAware Activity
class MyActivity : Activity(), DIAware {
    override val di: DI by lazy { (applicationContext as DIAware).di }
}

Using lateinit

Example: a DIAware Activity
class MyActivity : Activity(), DIAware {
    override lateinit var di: DI
    override fun onCreate(savedInstanceState: Bundle?) {
        di = (applicationContext as DIAware).di
    }
}

Using LateInitDI

If you don’t want the component classes to be DIAware, you can use a LateInitDI:

Example: an Activity with LateInitDI
class MyActivity : Activity() {
    val di = LateInitDI()
    override fun onCreate(savedInstanceState: Bundle?) {
        di.baseKodein = (applicationContext as DIAware).di
    }
}

Being Kodein-DI independent

The dependency holder pattern

If you want your components to be Kodein-DI independent, you can use the dependency holder pattern:

Example: The dependency holder pattern
class MyActivity : Activity() {

    class Deps(
            val ds: DataSource,
            val ctrl: controller
    )

    val deps by lazy { (applicationContext as MyApplication).creator.myActivity() }

    val ds by lazy { deps.ds }
    val ctrl by lazy { deps.ctrl }

    /* ... */
}

class MyApplication : Application() {

	interface Creator {
	    fun myActivity(): MyActivity.Deps
	}

	val creator: Creator = DICreator()

    /* ... */
}

class DICreator : MyApplication.Creator {

    private val di = DI {
        /* bindings */
    }.direct

    override fun myActivity() = di.newInstance { MyActivity.Deps(instance(), instance()) }
}

Proguard configuration

Since Kodein-DI 7.6 a new version of the type system has been enabled, to avoid some issue with R8 code elimination. However, this new feature allows proguard to optimize your dependencies, leading to runtime exceptions. Until a proper configuration is added to the right project you should add the following lines to your proguard configuration:

A DI View Model Factory
-keep, allowobfuscation, allowoptimization class org.kodein.type.TypeReference
-keep, allowobfuscation, allowoptimization class org.kodein.type.JVMAbstractTypeToken$Companion$WrappingTest

-keep, allowobfuscation, allowoptimization class * extends org.kodein.type.TypeReference
-keep, allowobfuscation, allowoptimization class * extends org.kodein.type.JVMAbstractTypeToken$Companion$WrappingTest