Kodein DI on Android
You can use Kodein as-is in your Android project or use the util library kodein-android
.
Kodein does work on Android as-is.
The kodein-android extension adds multiple android-specific utilities to Kodein.Using or not using this extension really depends on your needs. |
Have a look at the Android demo project!
Install
kodein-android
:-
Add this line in your
dependencies
block in your applicationbuild.gradle
file:implementation 'org.kodein.di:kodein-di-generic-jvm:6.5.5' implementation 'org.kodein.di:kodein-di-framework-android-???:6.5.5'
Kodein 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
Either kodein-di-generic-jvm
orkodein-di-erased-jvm
must be declared in addition to the Kodein-Android package.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 implementsKodeinAware
.Example: an Android Application class that implements KodeinAwareclass MyApp : Application(), KodeinAware { override val kodein by Kodein.lazy { (1) /* bindings */ } }
1 Using Kodein.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
Kodein
object with thekodein
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, use the dependency holder pattern.
Getting a Kodein object
You can always get the Kodein
object by using:
-
kodein()
inside an Android class (such asContext
,Activity
,Fragment
, etc.) -
kodein(context)
orkodein { context }
inside another class
The kodein function will only work if your Android Application class implements the KodeinAware interface.
|
The kodein result should be cached and not used multiple times in a row.
|
Being KodeinAware
It is very simple to have your Android classes be KodeinAware
:
class MyActivity : Activity(), KodeinAware {
override val kodein by kodein() (1)
val ds: DataSource by instance()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
ds.connect() (2)
/* ... */
}
}
1 | Retrieving the application Kodein instance via context. |
2 | Because everything is lazy, the kodein 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(), KodeinAware {
override val kodein by kodein()
override val kodeinTrigger = KodeinTrigger() (1)
val ds: DataSource by instance()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
kodeinTrigger.trigger() (2)
/* ... */
}
}
1 | Just create a trigger, and Kodein will automatically use it. |
2 | The kodein 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, you need an Android context.
For that, View Models need to implement AndroidViewModel
.
It is very easy to use Kodein inside View Models:
If you prefer your View Models to be independant from Kodein, you can use a View Model Factory. |
class MyViewModel(app: Application) : ApplicationViewModel(app), KodeinAware {
override val kodein by kodein() (1)
val repository : Repository by instance()
}
1 | Retrieving the application’s Kodein container. |
Android module
Kodein-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(), KodeinAware {
override val kodein by Kodein.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(), KodeinAware {
override val kodein by kodein()
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 Kodein context:
val inflater: LayoutInflater by kodein.on(getActivity()).instance()
or
class MyUtility(androidContext: Context) : KodeinAware {
override val kodein by androidContext.kodein()
override val kodeinContext = kcontext(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(), KodeinAware {
override val kodein by Kodein.lazy {
import(androidXContextTranslators) (1)
/* bindings */
}
}
1 | Can either be androidXContextTranslators or androidSupportContextTranslators or androidCoreContextTranslators . |
Android scopes
Component scopes
Kodein 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 kodein = Kodein {
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-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 kodein = Kodein {
bind<Controller>() with scoped(ActivityRetainedScope).singleton { ControllerImpl() }
}
This scope IS compatible with ScopeCloseable : see documentation.
|
Lifecycle scope
Kodein-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 kodein = Kodein {
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 Kodein 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 kodein
function will always return the kodein of the closest parent layer.
In a View
or a Fragment
, for example, it will return the containing Activity’s Kodein, if it defines one, else it will return the "global" Application Kodein.
In the following code example, if MyActivity
contains Fragments, and that these fragments get their Kodein object via kodein()
, they will receive the MyActivity
Kodein object, instead of the Application one.
Component based sub Kodein
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 allows you to create a Kodein
instance that lives only inside one of your components:
class MyActivity : Activity(), KodeinAware {
override val kodein by subKodein(kodein()) { (1)
/* activity specific bindings */
}
}
1 | Creating a sub Kodein 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 kodein is extended by defining the copy mode. In below example each instance of activity will store a copy of the kodein module (all bindings including Example: defining an Activity specific Kodein that copies all parent bindings
|
Activity retained sub Kodein
Kodein-Android provides retainedSubKodein
for Activities.
It creates a Kodein 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(), KodeinAware {
override val kodein: Kodein by retainedSubKodein(kodein()) { (1)
/* activity specific bindings */
}
}
1 | Using retainedSubKodein instead of subKodein ensures that the Kodein object is retained and not recreated between activity restarts. |
You can define the way the parent kodein is extended by defining the copy mode: Example: defining an Activity specific Kodein that copies all parent bindings
|
Independant Activity retained Kodein
Kodein provides the retainedKodein
function that creates a Kodein instance that is independendant from the parent.
This means that all bindings in the application context are NOT available through this new Kodein. |
class MyActivity : Activity() {
val activityKodein: Kodein by retainedKodein { (1)
/* activity specific bindings */
}
}
Kodein in Android without the extension
Being KodeinAware
It is quite easy to have your Android components being KodeinAware
(provided that your Application
class is KodeinAware
).
Using LateInitKodein
If you don’t want the component classes to be KodeinAware, you can use a LateInitKodein
:
class MyActivity : Activity() {
val kodein = LateInitKodein()
override fun onCreate(savedInstanceState: Bundle?) {
kodein.baseKodein = (applicationContext as KodeinAware).kodein
}
}
Being Kodein independant
The dependency holder pattern
If you want your components to be Kodein-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 = KodeinCreator()
/* ... */
}
class KodeinCreator : MyApplication.Creator {
private val kodein = Kodein {
/* bindings */
}.direct
override fun myActivity() = kodein.newInstance { MyActivity.Deps(instance(), instance()) }
}
View Model Factory
If you want your view models to be independant from Kodein, 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 KodeinViewModelFactory(val kodein: Kodein) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T =
kodein.direct.Instance(TT(modelClass))
}