Kodein DI

repository·main·Indexed 25 days ago

https://github.com/kosi-libs/kodein

A lightweight, straightforward dependency injection (DI) container for Kotlin. It supports Kotlin Multiplatform targets including JVM, Android, Native, JS, and Wasm. Kodein provides various binding types such as providers, singletons (including eager and referenced), factories, and multitons, as well as support for tagged bindings, external sources for fallback resolution, and DI-aware classes.

Tokens
24.8K
Snippets
104
Records
139
Agent score
86%

What's inside Kodein

  1. Overview of Kodein-DI

    main

    Kodein-DI is a dependency injection and retrieval container for Kotlin and Java. It is designed to be small, fast, and optimized using Kotlin inline functions.

    Key features include:

    • Lazy Instantiation: Dependencies are instantiated only when needed.
    • Order Independence: You do not need to manage the order of dependency initialization.
    • Flexible Binding: Easily bind classes or interfaces to instances, providers, or factories.
    • Type Safety: Unlike Java-based DI frameworks, it is not subject to type erasure.
    • Idiomatic Kotlin: Uses a declarative DSL and is highly optimized for Kotlin, while remaining usable in plain Java.
  2. Simulate map multi-binding using sets

    main

    Kodein-DI does not support map multi-binding directly. To achieve this, bind a set of Pair<K, V> (or a type alias) and then convert the resulting set to a map during retrieval using .toMap().

    typealias ConfigurationEntry = Pair<String, Configuration>
    typealias ConfigurationEntries = Set<ConfigurationEntry>
    
    val di = DI {
        bindSet<ConfigurationEntry> {
            add { singleton { "foo" to FooConfiguration() } }
            add { provider { "bar" to BarConfiguration() } }
        }
    }
    
    // Retrieve the map
    val configurations by di.instance<ConfigurationEntries>().toMap()
  3. Migrate Java projects from JavaX to Kodein DI

    main

    If you are migrating a Java project to Kotlin and want to use Kodein, you can use the kodein-jxinject extension to support existing javax.inject.* annotations (like those used by Guice or Dagger).

    CRITICAL: This guidance is for Java code only. Do not use these JxInject patterns in new Kotlin code; instead, use Kodein's native API.

  4. Use External Sources to provide fallback bindings

    main

    An ExternalSource provides an answer when Kodein-DI cannot find a binding for a required type, argument, or context. When a binding is missing, Kodein-DI iterates through the externalSources list in the order they were added and calls each source until one returns a non-null result.

    To implement an external source, use the ExternalSource { } constructor. The lambda receives a Key object containing information about the missing binding. You must return a function (typically created using the externalFactory utility) that takes an Any? argument and returns the instance. This returned function is called every time an instance is requested.

    Important Notes:

    • If no argument is provided to the binding, the argument passed to the factory lambda will be Unit.
    • Each ExternalSource is called only once per unknown key.
    • Return null from the ExternalSource lambda if it does not have an answer for the requested key.
    val di = DI {
        externalSources += ExternalSource { key ->
            when (key.type.raw) {
                Whatever::class -> when (key.argType.raw) {
                    Unit::class -> when (key.tag) {
                        "user" -> externalFactory { existingInstance }
                        null -> externalFactory { Whatever("default-value") }
                        else -> null
                    }
                    String::class -> when (key.tag) {
                        null -> externalFactory { Whatever(it as String) }
                        else -> null
                    }
                    else -> null
                }
                else -> null
            }
        }
    }
  5. Use the erased type system on JVM

    main

    On the JVM, the generic type system uses Kotlin's typeOf() and heavy reflection, which is less optimized. If you are working on the JVM and want to avoid reflection-based overhead, you can manually force the use of the erased version using the erased function forms.

    When to consider the erased version on JVM:

    • You are certain you are not binding, injecting, or retrieving generic types (and your libraries aren't either).
    • You are not using set-bindings.
    • You have profiled your code and identified injection as a performance bottleneck (though usually, performance issues in DI stem from object instantiation rather than retrieval).

    Warning: If you use the erased version on JS or Native, or choose it on the JVM, be aware of potential pitfalls related to type erasure.

  6. Define and Use Scopes

    main

    Scopes allow singletons or multitons to exist multiple times across different contexts. A scope is of type Scope<C>, where C is the context type. To retrieve a scoped binding, you must either provide the context explicitly or use a context translator/finder.

    Example of binding a User to a SessionScope (where SessionScope is a Scope<Session>):

    val di = DI {
        bind<User> { scoped(SessionScope).singleton { UserData(session.userId) } }
    }
    
    // Retrieval
    val user by di.on(session).instance()
  7. Install Kodein-DI

    main

    To use Kodein-DI, add the appropriate dependency to your build configuration. For JVM targets, ensure you are using at least JDK 1.8 (required since version 6.3.0).

    Maven

    Add the following to your pom.xml:

    Gradle

    Depending on your Gradle version, use one of the following methods:

    • Gradle 6+: Use implementation 'org.kodein.di:kodein-di:{version}'.
    • Gradle 5.x: Use implementation("org.kodein.di:kodein-di:{version}") in your .build.gradle.kts. You must also enable the GRADLE_METADATA preview feature in your settings.gradle.kts.
    • Gradle 4.x: Use implementation("org.kodein.di:kodein-di-jvm:{version}") in your .build.gradle.kts.
  8. Migrate Android Framework Modules

    main

    Android framework modules have been simplified. You no longer need to choose between erased and generic implementations. Use the unified Android modules.

    New Android Dependencies:

    • org.kodein.di:kodein-di-framework-android-core:{version}
    • org.kodein.di:kodein-di-framework-android-support:{version}
    • org.kodein.di:kodein-di-framework-android-x:{version}

    Class/Function Mapping:

    • RetainedKodeinFragment $\rightarrow$ RetainedDIFragment
    • closestKodein() $\rightarrow$ closestDI()
    • kodein() $\rightarrow$ di()
    • retainedKodein() $\rightarrow$ retainedDI()
    • subKodein() $\rightarrow$ subDI()
    • retainedSubKodein() $\rightarrow$ retainedSubDI()
  9. Copy bindings when extending DI instances

    main

    When using extend(parent), singletons in the parent do not automatically see overrides in the child. To allow a parent's singleton to use a child's overridden binding, you must explicitly copy that binding into the child container using the copy parameter in extend.

    Warning: Copying a singleton means it will exist twice (once in parent, once in child).

    // Copy a specific binding
    val child = DI {
        extend(parent, copy = Copy {
            copy the binding<Bar>()
        })
        bind<Foo>(overrides = true) { provider { Foo2() } }
    }
    
    // Copy a tagged scoped singleton
    val child = DI {
        extend(parent, copy = Copy {
            copy the binding<Session>() { scope(requestScope) and tag("req") }
        })
    }
    
    // Copy all bindings of a type
    val child = DI {
        extend(parent, copy = Copy {
            copy all binding<String>()
            copy all scope(requestScope)
        })
    }
    
    // Copy everything
    val child = DI {
        extend(parent, copy = Copy.All)
    }
    
    // Copy nothing
    val child = DI {
        extend(parent, copy = Copy.None)
    }