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 extension adds multiple android-specific utilities to Kodein-DI.Using or not using this extension really depends on your needs. |
Have a look at the Android demo project!
Install
kodein-di-android
:-
Add this line in your
dependencies
block in your applicationbuild.gradle
file:implementation 'org.kodein.di:kodein-di-framework-android-???:7.2.0'
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
From 6.3.0
On the JVM, you must be targetingJDK 1.8
minimum!If you are using SupportFragment
in your application, then you must use either the-support
or the-x
package. -
Declare the dependency bindings in the Android
Application
, having it implementsDIAware
.Example: an Android Application class that implements DIAwareclass MyApp : Application(), DIAware { override val di by DI.lazy { (1) /* bindings */ } }
1 Using DI.lazy
allows you to access theContext
at binding time.Don’t forget to declare the Application in the AndroidManifest.xml
file! -
In your Activities, Fragments, and other context aware android classes, retrieve the
DI
object with thedi
function. -
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 asContext
,Activity
,Fragment
, etc.) -
di(context)
ordi { 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
:
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:
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
To use Kodein-DI, you need an Android context.
For that, View Models need to implement AndroidViewModel
.
It is very easy to use Kodein-DI inside View Models:
If you prefer your View Models to be independant from Kodein-DI, you can use a View Model Factory. |
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. |
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 ;). |
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.
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:
val inflater: LayoutInflater by di.on(getActivity()).instance()
or
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:
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.
val di = DI {
bind<Controller>() with 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! |
val di = DI {
bind<Controller>() with 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.
val di = DI {
bind<Controller>() with 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:
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 Example: defining an Activity specific DI container that copies all parent 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! |
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
|
Independant 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. |
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 LateInitDI
If you don’t want the component classes to be DIAware, you can use a LateInitDI
:
class MyActivity : Activity() {
val di = LateInitDI()
override fun onCreate(savedInstanceState: Bundle?) {
di.baseKodein = (applicationContext as DIAware).di
}
}
Being Kodein-DI independant
The dependency holder pattern
If you want your components to be Kodein-DI independent, you can use 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()) }
}
View Model Factory
If you want your view models to be independant from 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:
class DIViewModelFactory(val di: DI) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T =
di.direct.Instance(erased(modelClass))
}