Kodein
for the first time, and the documentation is not transparent and consistent in all places, so I want to share the main features of the library with you. Some library features will be released, but this is basically the advanced part. Here you will find everything to start off normally and begin to implement dependencies with Kodein
as you read the article. The article is based on Kodein 5.3.0
, since Kodein 6.0.0
requires Support Library 28
or AndroidX
and not everyone will switch to them soon, since many third-party libraries do not yet offer compatible versions.Kodein
is a library for implementing dependency injection (DI). If you are not familiar with this concept, then read the beginning of the article on Dagger2 , where the author briefly explains the theoretical aspects of DI.type erasure
, a problem arises - the compiler erases the generic type. At the bytecode level, List<String>
and List<Date>
are simply List
. Still, there is a way to get information about generalized types, but it will be expensive to cost, and work only on JVM and Android. In this regard, Kodein
developers suggest using one of two dependencies: one at work gets information about generic types ( kodein-generic
), and the other not ( kodein-erased
). If the example, when using kodein-erased
List<String>
and List<Date
> will be saved as List<*>
, and when using kodein-generic
everything is saved together with the specified type, that is, as List<String>
and List<Date>
accordingly.kodein-erased
, otherwise it is impossible.kodein-erased
, but be careful, this experience may be unexpected in the bad sense of these words. If you are creating a regular application without special performance requirements, use kodein-generic
. buildscript { repositories { jcenter() } }
implementation "org.kodein.di:kodein-di-generic-jvm:$version"
implementation "org.kodein.di:kodein-di-erased-jvm:$version"
SupportLibrary
implementation "org.kodein.di:kodein-di-framework-android-core:$version"
implementation "org.kodein.di:kodein-di-framework-android-support:$version"
implementation "org.kodein.di:kodein-di-framework-android-x:$version"
Dagger2
, I’m used to creating and initializing dependencies when starting an application, in the Application class. class MyApp : Application() { val kodein = Kodein { /* */ } }
bind<TYPE>() with
Qualifier
from Dagger2
. In Dagger2
you need to do either a separate Qualifier
or use @Named("someTag")
, which in fact is also a Qualifier
. The essence is simple - in this way you distinguish two dependencies of the same type. For example, you need to get ontext
applications or a specific Activity
depending on the situation, therefore you need to specify tags when declaring dependencies. Kodein
allows Kodein
to declare one dependency without a tag, it will be basic, and if you do not specify a tag when a dependency is received, then we’ll get it, the rest must be tagged and when the dependency is received, the tag must be specified. val kodein = Kodein { bind<Context>() with ... bind<Context>(tag = "main_activity") with ... bind<Context>(tag = "sale_activity") with ... }
tag
parameter is of type Any
, so not only strings can be used. But remember that the classes used as tags must implement the equals
and hashCode
methods. You always need to pass a tag to a function as a named argument, whether you create a dependency or get it.Kodein
there are several ways to provide dependencies, let's start with the most necessary - creating singletons. Live singleton will be in the framework of the created copy of Kodein
. val kodein = Kodein { bind<IMyDatabase>() with singleton { RoomDb() } }
IMyDatabase
, for which the instance of RoomDb
will be hidden. The RoomDb
instance will be created at the first dependency request, it will not be re-created until a new Kodein
instance is Kodein
. Singleton is created synchronized, but if you wish, you can make and unsynchronized. This will increase productivity, but you must understand the risks that follow. val kodein = Kodein { bind<IMyDatabase>() with singleton(sync = false) { RoomDb() } }
Kodein
instance, use another function: val kodein = Kodein { bind<IMyDatabase>() with eagerSingleton { RoomDb() } }
provider
function: val kodein = Kodein { bind<IMainPresenter>() with provider { QuantityPresenter() } }
IMainPresenter
dependency, a new QuantityPresenter
will be created.factory
method. val kodein = Kodein { bind<IColorPicker>() with factory { r: Int, g: Int, b: Int, a: Int -> RgbColorPicker(r, g, b, a) } }
val kodein = Kodein { bind<IRandomIntGenerator>() with multiton { from: Int, to: Int -> IntRandom(from, to) } }
5
and 10
we will create a new instance of IntRandom(5, 10)
; if we re-invoke the dependency with the same parameters, we will get the previously created instance. Thus, a certain map
obtained from singletons with lazy initialization. Arguments, as is the case with the factory
maximum of 5 . val kodein = Kodein { bind<IRandomIntGenerator>() with multiton(sync = false) { from: Int, to: Int -> IntRandom(from, to) } }
singleton
or multiton
you can specify the type of reference to the stored instance. In the usual case that we considered above, this will be a regular strong
link. But it is possible to use soft
and weak
links. If you are not familiar with these concepts, then look here . val kodein = Kodein { bind<IMyMap>() with singleton(ref = softReference) { WorldMap() } bind<IClient>() with singleton(ref = weakReference) { id -> clientFromDB(id) } }
ref
. val kodein = Kodein { bind<Cache>() with singleton(ref = threadLocal) { LRUCache(16 * 1024) } }
Kodein
constants of simple types without inheritance or interfaces, for example, primitives or data classes. val kodein = Kodein { constant(tag = "maxThread") with 8 constant(tag = "serverURL") with "https://my.server.url"
bind
and use from
instead of with
. val kodein = Kodein { bind() from singleton { Gson() }
Gson
type dependency will be Gson
.Kodein
allows Kodein
to provide dependencies in different ways for the heirs of a certain class, or for classes that implement one interface. val kodein = Kodein { bind<Animal>().subTypes() with { animalType -> when (animalType.jvmType) { Dog::class.java -> eagerSingleton { Dog() } else -> provider { WildAnimal(animalType) } } }
Animal
class can be either a superclass or an interface, using .subtypes
we get an animalType
with a TypeToken<*>
, from which we can already get a Java class and, depending on it, provide dependencies in different ways. This feature can be useful if you use TypeToken
or its derivatives as a constructor parameter for a number of cases. Also this way you can avoid unnecessary code with the same creation of dependencies for different types. class ProductGateway(private val api: IProductApi, private val dispatchers: IDispatchersContainer) : IProductGateway
Kodein
enough to pass an instance () function call as parameters. The order of creation is not important. bind<IDispatchersContainer>() with singleton { DispatchersContainer() } bind<IProductGateway>() with singleton { ProductGateway(instance(), instance()) } bind<IProductApi>() with singleton { ProductApi() }
instance()
there may be provider()
or factory()
calls, more about these methods in the get and implement dependencies section.instance<TYPE>
can get a class that we will provide somewhere and call a method of this class to get a new dependency. bind<DataSource>() with singleton { MySQLDataSource() } bind<Connection>() with provider { instance<DataSource>().openConnection() }
Dagger2
, I'm used to cutting dependencies into modules. In Kodein
, at first glance, everything does not look very good. You need to create a lot of dependencies right in the Application
class and I personally don’t really like it. But there is a solution, Kodein
also allows you to create modules, and then connect them in those places where necessary. val appModule = Kodein.Module("app") { bind<Gson>() with singleton { provideGson() } bind<HttpClient>() with singleton { provideHttpClient() } } val kodein: Kodein = Kodein { import(appModule) bind<ISchedulersContainer>() with singleton { SchedulersContainer() } // }
Kodein
, then you will receive two different singletons, one for the copy of Kodein
. import(apiModule.copy(name = "firstAPI")) import(secondApiModule.copy(prefix = "secondAPI-"))
Kodein
can import each module once into Kodein
, so if you try to import two modules that have the same dependent modules into one Kodein
object, the application will crash. The output is simple - you need to use the importOnce(someModule)
call importOnce(someModule)
to import, which will check whether the module with the same name was previously imported, and then import if necessary. val appModule = Kodein.Module("app") { bind<Gson>() with singleton { provideGson() } } val secondModule = Kodein.Module("second") { import(appModule) } val thirdModule = Kodein.Module("third") { import(appModule) } val kodein: Kodein = Kodein { import(secondModule) import(thirdModule) }
val appModule = Kodein.Module("app") { bind<Gson>() with singleton { provideGson() } } val secondModule = Kodein.Module("second") { importOnce(appModule) } val thirdModule = Kodein.Module("third") { import(appModule) } val kodein: Kodein = Kodein { import(secondModule) import(thirdModule) }
importOnce
call is importOnce
when you try to connect again, then everything will work. Be careful. val appModule = Kodein.Module("app") { bind<Gson>() with singleton { provideGson() } } val secondModule = Kodein.Module("second") { import(appModule) } val thirdModule = Kodein.Module("third") { importOnce(appModule) } val kodein: Kodein = Kodein { import(secondModule) import(thirdModule) }
Subcomponents
in Dagger2
? It's simple, you just need to inherit from the instance of Kodein
and you will get access to all dependencies of the parent as a successor. val kodein: Kodein = Kodein { bind<ISchedulersContainer>() with singleton { SchedulersContainer() } // } val subKodein = Kodein { extend(kodein) // }
bind
function. This functionality will be useful, for example, for testing. val kodein = Kodein { bind<Api>() with singleton { ApiImpl() } /* ... */ bind<Api>(overrides = true) with singleton { OtherApiImpl() } }
Kodein
object, but you can specify when importing a module that its dependencies can override existing ones, and within this module you can specify dependencies that others can override. val appModule = Kodein.Module("app") { bind<Gson>() with singleton { provideGson() } } val kodein: Kodein = Kodein { bind<Gson>() with singleton { provideGson() } import(appModule) }
val appModule = Kodein.Module("app") { bind<Gson>() with singleton { provideGson() } } val kodein: Kodein = Kodein { bind<Gson>() with singleton { provideGson() } import(appModule, allowOverride = true) }
Kodein
object. val appModule = Kodein.Module("app") { bind<Gson>(overrides = true) with singleton { provideGson() } } val kodein: Kodein = Kodein { bind<Gson>() with singleton { provideGson() } import(appModule, allowOverride = true) }
Kodein
object Kodein
will override them and the application will not fall. We use the parameter allowSilentOverride
for the module. val testModule = Kodein.Module(name = "test", allowSilentOverride = true) { bind<EmailClient>() with singleton { MockEmailClient() } }
Kodein
developers share two ways to get dependencies - injection
and retieval
. In short, injection
is when a class receives all its dependencies when it is created, that is, into the constructor, and retrieval
is when the class itself is responsible for receiving its dependencies.injection
your class knows nothing about Kodein
and the code in the class is cleaner, but if you use retrieval
, then you have the opportunity to manage dependencies more flexibly. In the case of retrieval
all dependencies are obtained lazily, only at the moment of the first access to the dependency.Kodein
methods to get dependenciesKodein
class has three methods that will return a dependency to you, a dependency factory or dependency provider — this is instance()
, factory()
and provider()
respectively. Thus, if you provide a dependency with a factory
or provider
, then you can get not only the result of the function, but also the function itself. Do not forget that in all variants you can use tags. val kodein: Kodein = Kodein { bind<BigDecimal>() with factory { value: String -> BigDecimal(value) } bind<Random>() with provider { Random() } } private val number: BigDecimal by instance(arg = "23.87") private val numberFactory: (value: String) -> BigDecimal by factory() private val random: Random by instance() private val randomProvider: () -> Random by provider()
injection
. To implement, you must first remove all the dependencies of the class in its constructor, and then create an instance of the class using the call kodein.newInstance
class ProductApi(private val client: HttpClient, private val gson: Gson) : IProductApi class Application : Application() { val kodein: Kodein = Kodein { bind<Gson>() with singleton { provideGson() } bind<HttpClient>() with singleton { provideHttpClient() } } private val productApi: IProductApi by kodein.newInstance { ProductApi(instance(), instance()) } }
Kodein
instance, then the code from the example above will result in a Kodein.NotFoundException
. If you want to get the result of the function null
if there is no dependency, then there are three auxiliary functions for this: instanceOrNull()
, factoryOrNull()
and providerOrNull()
. class ProductApi(private val client: HttpClient?, private val gson: Gson) : IProductApi class Application : Application() { val kodein: Kodein = Kodein { bind<Gson>() with singleton { provideGson() } } private val productApi: IProductApi by kodein.newInstance { ProductApi(instanceOrNull(), instance()) } }
retrieval
, the initialization of all default dependencies is lazy. This allows you to get dependencies only at the moment when they are needed, and get dependencies in the classes that the system creates.Activity
, Fragment
and other classes with their own life cycle, it's all about them.Activity
we only need a link to the Kodein instance, after which we can use the already known methods. In fact, you have already seen retrieval
examples above, you just need to declare a property and delegate it to one of the functions: instance()
, factory()
or provider()
private val number: BigDecimal by kodein.instance(arg = "23.87") private val numberFactory: (value: String) -> BigDecimal by kodein.factory() private val random: Random? by kodein.instanceOrNull() private val randomProvider: (() -> Random)? by kodein.providerOrNull()
arg
instance
. , ( , 5 )? arg
M
, 2 5 . val kodein = Kodein { bind<IColorPicker>() with factory { r: Int, g: Int, b: Int, a: Int -> RgbColorPicker(r, g, b, a) } } val picker: IColorPicker by kodein.instance(arg = M(255, 211, 175, 215))
Kodein
, . val myTrigger = KodeinTrigger() val gson: Gson by kodein.on(trigger = myTrigger).instance() /*...*/ myTrigger.trigger() // Gson
val myTrigger = KodeinTrigger() val kodeinWithTrigger = kodein.on(trigger = myTrigger) val gson: Gson by kodeinWithTrigger.instance() /*...*/ myTrigger.trigger() // kodeinWithTrigger
Kodein
, LazyKodein
, , Kodein
. val kodein: Kodein = LazyKodein { Kodein { bind<BigDecimal>() with factory { value: String -> BigDecimal(value) } bind<Random>() with provider { Random() } } } private val number: BigDecimal by kodein.instance(arg = "13.4") /* ... */ number.toPlainString() // kodein
val kodein: Kodein = Kodein.lazy { bind<BigDecimal>() with factory { value: String -> BigDecimal(value) } bind<Random>() with provider { Random() } } private val number: BigDecimal by kodein.instance(arg = "13.4") /* ... */ number.toPlainString() // kodein
Kodein
LateInitKodein
. , , , baseKodein
, . val kodein = LateInitKodein() val gson: Gson by kodein.instance() /*...*/ kodein.baseKodein = /* Kodein */ /*...*/ gson.fromJson(someStr)
List
. . allInstances
, allProviders
, allFactories
. val kodein: Kodein = Kodein { bind<Number>() with singleton { Short.MAX_VALUE } bind<Double>() with singleton { 12.46 } bind<Double>("someTag") with singleton { 43.89 } bind<Int>() with singleton { 4562 } bind<Float>() with singleton { 136.88f } } val numbers: List<Number> by kodein.allInstances()
Kodein
, , Kodein
. class MyApplication : Application(), KodeinAware { override val kodein: Kodein = Kodein { bind<Number>() with singleton { Short.MAX_VALUE } bind<Double>() with singleton { 12.46 } bind<Double>("someTag") with singleton { 43.89 } bind<Int>() with singleton { 4562 } bind<Float>() with singleton { 136.88f } } val numbers: List<Number> by allInstances() }
by allInstances()
by kodein.allInstances()
KodeinAware
. class MyApplication : Application(), KodeinAware { override val kodein: Kodein = Kodein { bind<Number>() with singleton { Short.MAX_VALUE } bind<Double>() with singleton { 12.46 } bind<Double>("someTag") with singleton { 43.89 } bind<Int>() with singleton { 4562 } bind<Float>() with singleton { 136.88f } } override val kodeinTrigger = KodeinTrigger() val numbers: List<Number> by allInstances() override fun onCreate() { super.onCreate() kodeinTrigger.trigger() } }
Kodein
— Kodein
Kotlin lazy
. , , , Activity
. class CategoriesActivity : Activity(), KodeinAware { override val kodein: Kodein by lazy { (application as MyApplication).kodein } private val myFloat: Float by instance()
lateinit
. class CategoriesActivity : Activity(), KodeinAware { override lateinit var kodein: Kodein private val myFloat: Float by instance() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) kodein = (application as MyApplication).kodein }
DKodein
( direct). , , instance
, provider
. DKodein
Kodein . class MyApplication : Application(), KodeinAware { override val kodein: Kodein = Kodein { bind<BigDecimal>() with singleton { BigDecimal.TEN } } val directKodein: DKodein = kodein.direct val directKodein2: DKodein = Kodein.direct { bind<BigDecimal>() with singleton { BigDecimal.ONE } } val someNumber:BigDecimal = directKodein.instance() val someNumber2:BigDecimal = directKodein2.instance()
KodeinAware
, DKodein
DKodeinAware
, .Kodein
, — ( , Android).Kodein
, . , , .Activity
Presenter
, , Kodein
, , , . Activity
Presenter
— , . . : class MyApplication : Application(), KodeinAware { override val kodein: Kodein = Kodein { bind<BigDecimal>() with contexted<CategoriesActivity>().provider { context.getActivityBigDecimal() } bind<BigDecimal>() with contexted<CategoriesPresenter>().factory { initialValue:BigDecimal -> context.getPresenterBigDecimal(initialValue) } } } class CategoriesActivity : Activity(), AppKodeinAware { fun getActivityBigDecimal() = BigDecimal("16.34") private val activityBigDecimal: BigDecimal by kodein.on(context = this).instance() } class CategoriesPresenter : AppKodeinAware { fun getPresenterBigDecimal(initialValue: BigDecimal) = initialValue * BigDecimal.TEN private val presenterBigDecimal: BigDecimal by kodein.on(context = this).instance(arg = BigDecimal("31.74")) }
with provider()
, with contexted<OurContextClass>().provider
, OurContextClass
— , . contexted
provider factory.context
.Kodein
on()
, .injection
. private val productApi: IProductApi by kodein.on(context = someContext).newInstance { ProductApi(instance(), instance()) } }
Android
.Kodein
, .Application
KodeinAware
Kodein
( Application
). , Application
, Context
. — . class MyApplication : Application(), KodeinAware { override val kodein = Kodein.lazy { import(androidModule(this@MyApplication)) // } val inflater: LayoutInflater by instance() }
LayoutInflater
. — . val inflater: LayoutInflater by kodein.on(context = getActivity()).instance()
KodeinAware
, closestKodein()
Kodein
Application
. class MyActivity : Activity(), KodeinAware { override val kodein by closestKodein() val ds: DataSource by instance() }
closestKodein
Android , Android, . KodeinAware
— ( Android kcontext()
). class MyController(androidContext: Context) : KodeinAware { override val kodein by androidContext.closestKodein() override val kodeinContext = kcontext(androidContext) val inflater: LayoutInflater by instance() }
class MyActivity : Activity(), KodeinAware { private val parentKodein by closestKodein() override val kodein: Kodein by Kodein.lazy { extend(parentKodein) /* */ } }
retainedKodein
. Kodein
. class MyActivity : Activity(), KodeinAware { private val parentKodein by closestKodein() override val kodein: Kodein by retainedKodein { extend(parentKodein) } }
Source: https://habr.com/ru/post/431696/
All Articles