📜 ⬆️ ⬇️

Kodein. The basics

I did not find understandable guides for those who see 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.

In this article we will look at everything using the example of Android, but, according to the developers, Kodein behaves the same on all platforms that are supported by Kotlin (JVM, Android, JS, Native).

Installation


Due to the fact that in Java there is a 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.
')
How to choose?

Write not under JVM - use kodein-erased , otherwise it is impossible.
Write under the JVM and the issue of performance is very important to you - you can use 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 .

Ultimately, if you think about the impact of DI on performance, then most of the time most of the dependencies are created once, or the dependencies are created for repeated reuse, it is unlikely that with such actions you can greatly affect the performance of your application.

So, install:

The first - in build.gradle among repositories should be jcenter (), if it is not there - add it.

 buildscript { repositories { jcenter() } } 

Next, add one of the basic dependencies mentioned above to the dependencies block:

 implementation "org.kodein.di:kodein-di-generic-jvm:$version" 

 implementation "org.kodein.di:kodein-di-erased-jvm:$version" 

As we are talking about Android, there will be more dependencies. Of course, you can do without it, Kodein will function normally, but why refuse additional features that are useful for Android (I’ll tell about them at the end of the article)? The choice is yours, but I suggest adding.

There are also options here.

First, you do not use SupportLibrary

 implementation "org.kodein.di:kodein-di-framework-android-core:$version" 

The second is using

 implementation "org.kodein.di:kodein-di-framework-android-support:$version" 

Third - you are using AndroidX

 implementation "org.kodein.di:kodein-di-framework-android-x:$version" 

Begin to create dependencies


Using Dagger2 , I’m used to creating and initializing dependencies when starting an application, in the Application class.

With Kodein it is done like this:

 class MyApp : Application() { val kodein = Kodein { /*  */ } } 

Declaring dependencies always starts with

 bind<TYPE>() with 

Tags


Tagging dependencies in Kodein is a feature similar in functionality to 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 ... } 

The 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.

Types of dependency injection


In 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 .

We implement singletons


Let's start with an example:

 val kodein = Kodein { bind<IMyDatabase>() with singleton { RoomDb() } } 

Thus, we provide (provide) 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() } } 

If there is a need to create a dependency instance not on the first call, but immediately after creating a Kodein instance, use another function:

 val kodein = Kodein { bind<IMyDatabase>() with eagerSingleton { RoomDb() } } 

Constantly create a new instance of dependency.


It is possible to create not singletones, but constantly, when accessing a dependency, to receive its new instance. To do this, use the provider function:

 val kodein = Kodein { bind<IMainPresenter>() with provider { QuantityPresenter() } } 

In this case, each time we request an IMainPresenter dependency, a new QuantityPresenter will be created.

Constantly create a new instance of dependency and pass the parameters to the dependency designer


With each request for a dependency, you can receive a new instance of it, as in the previous example, but still specify the parameters for creating a dependency. Parameters can be a maximum of 5 . For this behavior, use the factory method.

 val kodein = Kodein { bind<IColorPicker>() with factory { r: Int, g: Int, b: Int, a: Int -> RgbColorPicker(r, g, b, a) } } 

Each time we create a cached instance depending on the parameters.


Reading the previous paragraph, you might think that it would be nice not to receive a new instance every time according to the passed parameters, but to receive the same instance of the dependency using the same parameter.

 val kodein = Kodein { bind<IRandomIntGenerator>() with multiton { from: Int, to: Int -> IntRandom(from, to) } } 

In the example above, when we first receive a dependency with parameters 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 .

As in the case of singletons, you can disable synchronization here.

 val kodein = Kodein { bind<IRandomIntGenerator>() with multiton(sync = false) { from: Int, to: Int -> IntRandom(from, to) } } 

Using Soft and Weak Links in Kodein


When providing dependencies using 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 .

Thus, your singletons may or may not be re-created within the application life cycle.

 val kodein = Kodein { bind<IMyMap>() with singleton(ref = softReference) { WorldMap() } bind<IClient>() with singleton(ref = weakReference) { id -> clientFromDB(id) } } 

Separate singleton for each stream


This is the same singleton, but for each stream requesting a dependency, a singleton will be created. To do this, use the already familiar parameter ref .

 val kodein = Kodein { bind<Cache>() with singleton(ref = threadLocal) { LRUCache(16 * 1024) } } 

Constants as implemented dependencies


It is possible to provide constants as dependencies. The documentation draws attention to the fact that you must use 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" 

Create dependencies without changing the type


For example, you want to provide a dependency as a singleton, but do not hide it behind the interface. You can simply omit the type when calling bind and use from instead of with .

 val kodein = Kodein { bind() from singleton { Gson() } 

The dependency in the example above will have the return type of the function, that is, the Gson type dependency will be Gson .

Create subtype dependencies of a superclass or interface


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) } } } 

The 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.

Create dependencies that need other dependencies as parameters


Most often, we do not just create a class without parameters as a dependency, but create a class that needs to pass parameters to the constructor.

 class ProductGateway(private val api: IProductApi, private val dispatchers: IDispatchersContainer) : IProductGateway 

In order to create a class with dependencies that were previously created in 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() } 

Instead of instance() there may be provider() or factory() calls, more about these methods in the get and implement dependencies section.

Create a dependency by calling the previously created dependency method


It does not sound very much, but by calling 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() } 

Modules


Using 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() } //   } 

But be careful, modules are just containers that declare methods for getting dependencies, they don't create classes themselves. Therefore, if in the module you declare the receipt of a dependency as a singleton, and then import this module into two different copies of Kodein , then you will receive two different singletons, one for the copy of Kodein .

Also, the name of each module must be unique. However, if you need to import a module from another project, it is difficult to guarantee the uniqueness of the name, for this it is possible to rename the module or add a prefix to its name.

 import(apiModule.copy(name = "firstAPI")) import(secondApiModule.copy(prefix = "secondAPI-")) 

I am accustomed to work when the modules depend on each other and constitute a hierarchy. 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.

For example, in such cases, the application will fall:

  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) } 

But if the 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) } 

Inheritance


If you use one module twice, different dependencies will be created, but what about inheritance and implement behavior similar to 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) //   } 

Override


By default, it is impossible to override the dependency, otherwise users would go crazy, looking for reasons for the incorrect operation of the application. But it is possible to do this with the help of an additional parameter of the 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() } } 

By default, modules and their dependencies cannot override dependencies already declared in the 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.

It doesn't sound quite clear, let's use examples. In these cases, the application will crash:

  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) } 

And in this module dependency will overwrite the dependency declared in the 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) } 

But if you really want to and you understand what you are doing, then you can create such a module that, if there are identical dependencies with the 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() } } 

The documentation deals with more complex situations with inheritance and redefinition of dependencies, as well as with copying dependencies in the heirs, but here these situations will not be considered.

Extract and inject dependencies


Finally, we figured out how to declare dependencies in a variety of ways, it's time to figure out how to get them in our classes.

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.

When using 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 dependencies


An instance of the Kodein 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() 

Implementing dependencies through the constructor


As you already understood, it will be about 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()) } } 

Embedding dependencies in nullable properties


It may well be a situation where you do not know whether a dependency has been declared. If the dependency is not declared in the 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()) } } 

We receive dependences in a class


As already mentioned, in the case when we use 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.

To embed dependencies in the 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() 

Passing parameters to the factory


, , 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


Kodein , LazyKodein , , Kodein .

, , , , 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    

Kodein.lazy

  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


Kodein LateInitKodein . , , , baseKodein , .

 val kodein = LateInitKodein() val gson: Gson by kodein.instance() /*...*/ kodein.baseKodein = /*     Kodein */ /*...*/ gson.fromJson(someStr) 


Kodein 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() 

, [32767, 136.88, 4562, 12.46]. .

KodeinAware


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() } } 

KodeinKodein 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() 

Kodein 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


Android .
Kodein , .

Kodein Android


— , Android. , 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 . — .

Android , — .

 val inflater: LayoutInflater by kodein.on(context = getActivity()).instance() 

Kodein closestKodein()


, Android . Application, Activity, Fragment. Activity 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() } 

Kodein Activity


Kodein Activity . .

 class MyActivity : Activity(), KodeinAware { private val parentKodein by closestKodein() override val kodein: Kodein by Kodein.lazy { extend(parentKodein) /*   */ } } 

Kodein,


, . 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