Koin Annotations

repository·main·Indexed 42 days ago

https://github.com/insertkoinio/koin-annotations

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

Tokens
16.5K
Snippets
68
Records
75
Agent score
53%

What's inside Koin Annotations

  1. What is Koin Annotations?

    main
    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.
  2. Share definitions with different native contracts using Interfaces

    main

    If your platform-specific implementations require different constructor arguments, expect/actual classes cannot be used directly because they must respect a single constructor contract defined in commonMain.

    Instead, use an Interface pattern:

    1. Define an interface in commonMain.
    2. Define an expect class @Module in commonMain that provides the interface via a function.
    3. Implement the actual class @Module in 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}"
    }
  3. Distinguish definitions with @Named and @Qualifier

    main

    Use 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: @Qualifier has 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)
  4. Organize modules using @Configuration

    main

    The @Configuration annotation allows you to group @Module classes 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 @Configuration or @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: @KoinApplication will scan and load these modules based on the configurations array 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()
    }
  5. Resolve dependencies from a scope

    main

    A component defined within a scope can resolve dependencies from two places:

    1. The current scope: Other components defined within the same scope.
    2. Parent scopes: Components defined in the Koin root (e.g., @Single definitions).

    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
    )
  6. Use Native Module Scanning for unconstrained sharing

    main

    If you want to avoid defining strict constraints in commonMain, you can define an empty expect class @Module in your common source set.

    On each native platform, provide an actual class @Module and use @ComponentScan to 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 NativeModuleC
  7. Share definitions using Expect/Actual classes

    main

    When your platform-specific components share the same constructor contract, you can use the expect/actual pattern directly on the class marked with @Factory or @Single.

    Pattern 1: Scanning for Expect/Actual definitions

    Use @ComponentScan on an @Module in commonMain to scan a package containing expect classes. Implement the actual class in each native source set.

    Pattern 2: Declaring Expect/Actual function definitions

    Define a function within an @Module in commonMain that returns an expect class. Implement the actual class 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"
    }
  8. Use Android-specific scope annotations

    main

    Koin provides specialized scope annotations for Android components to manage their lifecycles:

    • @ViewModelScope: For components living within a ViewModel's lifecycle.
    • @ActivityScope: For components living within an Activity scope.
    • @ActivityRetainedScope: For components in an Activity scope that are retained across configuration changes.
    • @FragmentScope: For components living within a Fragment scope.

    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)
  9. Core Koin Annotation Definitions

    main

    Koin 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 to single { }).
    • @Factory: Declares a factory instance that is recreated every time it is requested (equivalent to factory { }).
    • @KoinViewModel: Declares an Android ViewModel instance (equivalent to viewModel { }).
    • @KoinWorker: Declares an Android Worker Workmanager instance (equivalent to worker { }).

    For Scopes, refer to the Koin Core documentation.

    @Single
    class MyComponent(val myDependency : MyDependency)
  10. Organize Koin Modules and Components

    main

    Koin Annotations uses specific annotations to define dependency injection structures:

    • @Module: Marks a class as a Koin module containing definitions.
    • @ComponentScan: Used on a @Module to 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 Json or HttpClient instance).

  11. Report bugs and questions

    main

    Koin uses GitHub issues for tracking bugs and enhancements.

    • General Questions: Open a GitHub issue with the Question tag or ask on Stack Overflow using the koin tag.
    • 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.