Simple DI with compile-time dependency graph validation for kotlin multiplatform. It uses IR to create method's bodies with dependency injection.
Limitations: all annotations required for generating functions should be available in the same file as generated function. It can use methods and constructors from outside, but not annotations, because adding and removing annotations in other files would not trigger recompilation for generated function and combined with incremental compilation it would cause errors.
DI.kt is smaller and simpler than some solutions, but it verifies dependency graph in compile time like a serious DI framework.
DI.kt does not generate files during compilation, which makes compilation faster (presumably, not tested).
Because of its simplicity it might be useful for minimalistic DI in libraries and feature-modules, but it can be used in big project too.
Kotlin-inject - incredibly powerful DI framework with Dagger-like api;
Koin, Kodein-DI and PopKorn - service locators with great versatility, but without compile time error detection that we used to have in Dagger;
Dagger - most popular DI framework for Android, but it doesn't support multiplatform yet.
I forked kotlin multiplatform sample here and replaced di with DI.kt. It's clumsy, but it shows that library works on different platforms.
DI.kt, One of the First Kotlin Multiplatform DI Libraries
Because library uses undocumented compiler api that often changes each library version works well only with specific kotlin versions, check table bellow to decide witch library version to use.
DI.kt version | Supported kotlin versions |
---|---|
1.0.3 | 1.8.0 - 1.8.10 (k2 with hacks) |
1.0.2 | 1.7.0 - 1.7.21 |
1.0.1 | 1.6.2x |
1.0.0-alpha9 | 1.6.10 |
1.0.0-alpha7 | 1.6.0 |
Version 1.0.3-alpha2 has limited support for k2, but requires hack to work properly - annotation @Suppress("NON_ABSTRACT_FUNCTION_WITH_NO_BODY") is required for module or each di function because plugin can't bypass function body check on k2 for now.
In build.gradle file in module add plugin:
plugins {
...
id 'io.github.sergeshustoff.dikt' version '1.0.2'
}
Install idea plugin, it will remove errors from ide for methods with generated body.
Create module and declare provided dependencies. Use @Create
, @Provide
, @CreateSingle
and @ProvideSingle
to generate function's bodies. Use @UseModule
s and @UseConstructors
to control how dependencies are provided and what classes can be created by primary constructors.
class SomethingModule(
val externalDependency: Something,
) {
@CreateSingle fun someSingleton(): SomeSingleton
@Create fun provideSomethingElse(): SomethingElse
}
Under the hood primary constructor will be called for SomethingElse and SomeSingleton. If constructor requires some parameters - they will be retrieved form module's properties and functions.
Any class or object that has a function marked with @Create
, @Provide
, @CreateSingle
or @ProvideSingle
is essentially a module. We don't need additional annotation for it, but if you need content of another 'module' provided as dependency in generated functions, you need to mark that type as module using annotation @UseModules
on function, its containing class or file.
There are no true singletons in DI.kt, but instead you can use @CreateSingle
or @ProvideSingle
annotations to generate functions backed by lazy properties. Such function will return the same instance each time they called as long as they called for the same instance of containing class. Effectively it gives each module a scope of their own and makes the scoping more understandable.
Magical annotation that tells compiler plugin to generate method body using returned type's primary constructor. Values for constructor parameters will be retrieved from function parameters and from functions and properties of containing class.
Code generated by this annotation always uses returned type's primary constructor, even if dependency of returned type is available in parameters or in containing class.
class Something(val name: String)
@Create fun provideSomething(name: String): Something
Code above will be transformed into
fun provideSomething(name: String) = Something(name)
Tells compiler plugin to generate method body that returns value of specified type retrieved from dependencies. For example from containing class properties or functions.
It's useful for elevating dependencies from nested modules.
Doesn't call constructor for returned type unless it's listed in @UseConstructors
.
class Something(val name: String)
class ExternalModule(
val something: Something
)
@UseModules(ExternalModule::class)
class MyModule(val external: ExternalModule) {
@Provide fun provideSomething(): Something
}
Same as @Create
and @Provide
, but each annotation tells compiler to create a lazy property in containing class and return value from that property. Functions marked with @CreateSingle
and @ProvideSingle
don't support parameters.
Dependencies of types listed in this annotation parameters will be provided by constructor when required.
Might be applied to file, class, or @Create
or @Provide
function.
When constructor called for returned type of @Create
function requires parameter of type listed in @UseConstructors
it's constructor will be called instead of looking for provided dependency of that type.
class SomeDependency
class Something(val dependency: SomeDependency)
@UseConstructors(SomeDependency::class)
class MyModule {
@Create fun provideSomething(): Something
}
Marks types that should provide all visible properties and functions as dependencies. Such dependencies can be used in @Create
function as constructor parameters or in @Provide
function as returned type.
Listed type should be available from DI function in order to provide type's properties and functions.
WARNING: This annotation doesn't work recursively. It means that function can only use modules listed in its own annotation or in its class annotation or in its file annotation.
class ExternalModule(val name: String)
class Something(val name: String)
@UseModules(ExternalModule::class)
class MyModule(
private val external: ExternalModule
) {
@Create fun provideSomething(): Something // will call constructor using external.name as parameter
}