Koin Annotations
repository·main·Indexed 42 days ago
https://github.com/insertkoinio/koin-annotationsA library that adds annotation-based dependency injection to the Koin framework using Google KSP. It allows developers to define dependencies via annotations like @Single, @Factory, @Module, and @KoinViewModel instead of manual DSL module declarations, supporting Kotlin Multiplatform, Android, and JVM/Native targets.
What's inside Koin Annotations
- Koin Annotations is an extension for the Koin dependency injection framework that brings annotation-based dependency injection capabilities to your projects. It is powered by Google KSP (Kotlin Symbol Processing) to generate Koin modules from annotations, providing a more declarative way to manage dependencies compared to manual module definitions.
Share definitions with different native contracts using Interfaces
mainIf your platform-specific implementations require different constructor arguments,
expect/actualclasses cannot be used directly because they must respect a single constructor contract defined incommonMain.Instead, use an Interface pattern:
- Define an
interfaceincommonMain. - Define an
expect class @ModuleincommonMainthat provides the interface via a function. - Implement the
actual class @Modulein each native source set, returning a platform-specific implementation of the interface.
Warning: Using
scope.get()inside these provider functions performs dynamic wiring, which is not covered by compile-time safety.// commonMain expect class NativeModuleD() { @Factory fun providesPlatformComponentD(scope : org.koin.core.scope.Scope) : PlatformComponentD } interface PlatformComponentD { fun sayHello() : String } // androidMain @Module actual class NativeModuleD { @Factory actual fun providesPlatformComponentD(scope : org.koin.core.scope.Scope) : PlatformComponentD = PlatformComponentDAndroid(scope) } class PlatformComponentDAndroid(scope : org.koin.core.scope.Scope) : PlatformComponentD { val context : Context = scope.get() override fun sayHello() : String = "Android with ${context}" }- Define an
Distinguish definitions with @Named and @Qualifier
mainUse these annotations to distinguish between multiple definitions of the same type using a qualifier.
@Named
Parameters:
value: String: The string qualifier.type: KClass<*>: The class qualifier.
@Qualifier
Parameters:
value: KClass<*>: The class qualifier.name: String: The string qualifier.
Note:
@Qualifierhas reversed parameter priority compared to@Named.// Using @Named with a string @Single @Named("special") class MyClass(val d : MyDependency) // Using @Named in a parameter @Single class Consumer(@Named("special") val myClass: MyClass) // Using @Named with a type @Single @Named(type = MyType::class) class MyClass(val d : MyDependency) // Using @Qualifier with a name @Single @Qualifier(name = "special") class MyClass(val d : MyDependency)Organize modules using @Configuration
mainThe
@Configurationannotation allows you to group@Moduleclasses into named environments or feature sets (e.g.,"prod","test","development"). This enables loading different sets of dependencies based on the application context.- Default Configuration: An empty
@Configurationor@Configuration("default")assigns the module to the"default"configuration. - Multiple Configurations: A single module can belong to multiple configurations by providing multiple strings to the annotation.
- Scanning:
@KoinApplicationwill scan and load these modules based on theconfigurationsarray provided to it.
// Available in both "prod" and "test" @Module @Configuration("prod", "test") class DatabaseModule { @Single fun database() = PostgreSQLDatabase() } // Available in default, test, and development @Module @Configuration("default", "test", "development") class LoggingModule { @Single fun logger() = Logger() }- Default Configuration: An empty
Resolve dependencies from a scope
mainA component defined within a scope can resolve dependencies from two places:
- The current scope: Other components defined within the same scope.
- Parent scopes: Components defined in the Koin root (e.g.,
@Singledefinitions).
This allows scoped components to access global singletons and other sibling scoped components seamlessly.
@Single class MySingle @Scope(name = "my_scope_name") @Scoped class MyScopedComponent( val mySingle : MySingle, // Resolved from root val myOtherScopedComponent : MyOtherScopedComponent // Resolved from current scope ) @Scope(name = "my_scope_name") @Scoped class MyOtherScopedComponent( val mySingle : MySingle )Use Native Module Scanning for unconstrained sharing
mainIf you want to avoid defining strict constraints in
commonMain, you can define an emptyexpect class @Modulein your common source set.On each native platform, provide an
actual class @Moduleand use@ComponentScanto pick up components specific to that platform. This allows the native side to remain decoupled from the common side's structure while still participating in the Koin dependency graph.// commonMain @Module expect class NativeModuleC() // androidMain @Module @ComponentScan("com.example.android") actual class NativeModuleC // com.example.android @Factory class PlatformComponentC(val context: Context) { fun sayHello() : String = "Android component" } // iOSMain @Module actual class NativeModuleCShare definitions using Expect/Actual classes
mainWhen your platform-specific components share the same constructor contract, you can use the
expect/actualpattern directly on the class marked with@Factoryor@Single.Pattern 1: Scanning for Expect/Actual definitions
Use
@ComponentScanon an@ModuleincommonMainto scan a package containingexpectclasses. Implement theactualclass in each native source set.Pattern 2: Declaring Expect/Actual function definitions
Define a function within an
@ModuleincommonMainthat returns anexpectclass. Implement theactualclass in native source sets.// commonMain @Module @ComponentScan("com.example.native") class NativeModuleA() @Factory expect class PlatformComponentA() { fun sayHello() : String } // androidMain @package com.example.native actual class PlatformComponentA { actual fun sayHello() : String = "I'm Android" }Use Android-specific scope annotations
mainKoin provides specialized scope annotations for Android components to manage their lifecycles:
@ViewModelScope: For components living within aViewModel's lifecycle.@ActivityScope: For components living within anActivityscope.@ActivityRetainedScope: For components in anActivityscope that are retained across configuration changes.@FragmentScope: For components living within aFragmentscope.
These annotations are meant to be used with their corresponding Koin functions (e.g.,
viewModelScope,activityScope) to activate the scope.@ViewModelScope class MyViewModelClass(val d : MyDependency) @ActivityScope class MyActivityClass(val d : MyDependency) @ActivityRetainedScope class MyRetainedClass(val d : MyDependency) @FragmentScope class MyFragmentClass(val d : MyDependency)Core Koin Annotation Definitions
mainKoin Annotations allow you to declare dependencies using annotations instead of the manual Koin DSL. The following annotations map to specific DSL declarations:
@Single: Declares a singleton instance (equivalent tosingle { }).@Factory: Declares a factory instance that is recreated every time it is requested (equivalent tofactory { }).@KoinViewModel: Declares an Android ViewModel instance (equivalent toviewModel { }).@KoinWorker: Declares an Android Worker Workmanager instance (equivalent toworker { }).
For Scopes, refer to the Koin Core documentation.
@Single class MyComponent(val myDependency : MyDependency)Organize Koin Modules and Components
mainKoin Annotations uses specific annotations to define dependency injection structures:
@Module: Marks a class as a Koin module containing definitions.@ComponentScan: Used on a@Moduleto scan specific packages for annotated components (e.g.,@Single,@Factory). This is useful for discovering components across different Gradle modules.@Single: Marks a class as a singleton component.@KoinViewModel: Marks a class as a Koin ViewModel component.@Factory: Marks a class as a factory component (a new instance is created every time it is requested).
Components can also be defined inside annotated Kotlin functions (for example, when configuring a
JsonorHttpClientinstance).Setup JSR-330 Compatibility
mainTo use JSR-330 (Jakarta Inject) annotations (like
@Singleton,@Named,@Inject) for compatibility with frameworks like Hilt or Dagger, add thekoin-jsr330dependency to your project.dependencies { implementation "io.insert-koin:koin-jsr330:$koin_version" }Report bugs and questions
mainKoin uses GitHub issues for tracking bugs and enhancements.
- General Questions: Open a GitHub issue with the
Questiontag or ask on Stack Overflow using thekointag. - Bug Reports: To speed up diagnosis, provide as much information as possible, ideally including a small sample project or a Gist that reproduces the issue.
- General Questions: Open a GitHub issue with the