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