CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-io-insert-koin--koin-compose

Jetpack Compose integration for Koin dependency injection framework providing Compose-specific APIs for dependency injection

Pending
Overview
Eval results
Files

dependency-injection.mddocs/

Dependency Injection

Functions for injecting dependencies into Composable functions with type safety and performance optimization through Compose's remember system. The koinInject function provides three overloads to handle different parameter scenarios.

Capabilities

Basic Dependency Injection

Resolve Koin dependency for a given type without parameters.

/**
 * Resolve Koin dependency for given Type T
 *
 * @param qualifier - dependency qualifier
 * @param scope - Koin's root by default
 * @return instance of type T
 */
@Composable
inline fun <reified T> koinInject(
    qualifier: Qualifier? = null,
    scope: Scope = currentKoinScope()
): T

Usage Examples:

import org.koin.compose.koinInject

@Composable
fun UserScreen() {
    // Basic injection
    val userRepository: UserRepository = koinInject()
    
    // With qualifier
    val apiClient: ApiClient = koinInject(qualifier = named("public"))
    
    // With specific scope
    val sessionService: SessionService = koinInject(scope = someSpecificScope)
    
    // Use the injected dependencies
    LaunchedEffect(Unit) {
        val users = userRepository.getUsers()
        // Handle users...
    }
}

Parameterized Dependency Injection with Lambda

Resolve Koin dependency with parameters provided via lambda. Note: This version unwraps parameters to ParametersHolder and will be triggered on recomposition.

/**
 * Resolve Koin dependency for given Type T
 *
 * Note: this version unwrap parameters to ParametersHolder in order to let remember all parameters
 * This parameters unwrap will be triggered on recomposition
 *
 * For better performances we advise to use koinInject(Qualifier,Scope,ParametersHolder)
 *
 * @param qualifier - dependency qualifier
 * @param scope - Koin's root by default
 * @param parameters - injected parameters (with lambda & parametersOf())
 * @return instance of type T
 */
@Composable
inline fun <reified T> koinInject(
    qualifier: Qualifier? = null,
    scope: Scope = currentKoinScope(),
    noinline parameters: ParametersDefinition
): T

Usage Examples:

import org.koin.compose.koinInject
import org.koin.core.parameter.parametersOf

@Composable
fun UserDetailScreen(userId: String) {
    // Inject with parameters using lambda
    val userService: UserService = koinInject { 
        parametersOf(userId, "detailed") 
    }
    
    // More complex parameters
    val analyticsService: AnalyticsService = koinInject(
        qualifier = named("user_analytics")
    ) { 
        parametersOf(userId, System.currentTimeMillis()) 
    }
    
    // Use the services
    LaunchedEffect(userId) {
        val user = userService.getUser()
        analyticsService.trackUserView(user)
    }
}

Parameterized Dependency Injection with ParametersHolder

Resolve Koin dependency with pre-created parameters. This approach provides better performance as parameters are not unwrapped on each recomposition.

/**
 * Resolve Koin dependency for given Type T
 *
 * @param qualifier - dependency qualifier
 * @param scope - Koin's root by default
 * @param parametersHolder - parameters (used with parametersOf(), no lambda)
 * @return instance of type T
 */
@Composable
inline fun <reified T> koinInject(
    qualifier: Qualifier? = null,
    scope: Scope = currentKoinScope(),
    parametersHolder: ParametersHolder
): T

Usage Examples:

import org.koin.compose.koinInject
import org.koin.core.parameter.parametersOf
import androidx.compose.runtime.remember

@Composable
fun UserDetailScreen(userId: String) {
    // Create parameters holder once
    val userParams = remember(userId) { 
        parametersOf(userId, "detailed") 
    }
    
    // Inject with parameters holder (better performance)
    val userService: UserService = koinInject(
        parametersHolder = userParams
    )
    
    // Multiple services with same parameters
    val userCache: UserCache = koinInject(
        qualifier = named("memory_cache"),
        parametersHolder = userParams
    )
    
    // Use the services
    LaunchedEffect(userId) {
        val user = userService.getUser()
        userCache.store(user)
    }
}

Performance Considerations

Remember Integration

All koinInject functions use Compose's remember internally for performance optimization:

// Internal implementation uses remember for caching
return remember(qualifier, scope) {
    scope.get(T::class, qualifier)
}

// With parameters, both qualifier, scope, and parameters are remembered
return remember(qualifier, scope, parametersHolder) {
    scope.getWithParameters(T::class, qualifier, parametersHolder)
}

Parameter Performance

For best performance with parameters:

  1. Use ParametersHolder version when possible (third overload)
  2. Remember parameter creation when using lambda version
  3. Avoid recreating parameters on every recomposition
// ❌ Poor performance - parameters recreated on every recomposition
val service: Service = koinInject { parametersOf(userId, timestamp) }

// ✅ Better performance - parameters remembered
val params = remember(userId) { parametersOf(userId, timestamp) }
val service: Service = koinInject(parametersHolder = params)

Qualifier Usage

Named Qualifiers

import org.koin.core.qualifier.named

@Composable
fun ApiScreen() {
    val publicApi: ApiClient = koinInject(qualifier = named("public"))
    val adminApi: ApiClient = koinInject(qualifier = named("admin"))
    val internalApi: ApiClient = koinInject(qualifier = named("internal"))
}

String Qualifiers

import org.koin.core.qualifier.StringQualifier

@Composable  
fun DatabaseScreen() {
    val readOnlyDb = koinInject<Database>(qualifier = StringQualifier("readonly"))
    val writeDb = koinInject<Database>(qualifier = StringQualifier("write"))
}

Scope Usage

Default Scope

By default, koinInject uses the current composition's scope:

@Composable
fun MyScreen() {
    // Uses currentKoinScope() by default
    val service: MyService = koinInject()
}

Custom Scope

@Composable
fun ScopedComponent() {
    val customScope = remember { getKoin().createScope<MyScope>("custom_id") }
    
    val scopedService: ScopedService = koinInject(scope = customScope)
    
    DisposableEffect(customScope) {
        onDispose {
            customScope.close()
        }
    }
}

Dependency Types

// Core parameter types
class ParametersHolder
typealias ParametersDefinition = () -> ParametersHolder

// Qualifier types  
interface Qualifier
class StringQualifier(val value: String) : Qualifier
fun named(name: String): Qualifier

// Scope types
interface Scope {
    fun <T : Any> get(clazz: KClass<T>, qualifier: Qualifier?): T
    fun <T : Any> getWithParameters(clazz: KClass<T>, qualifier: Qualifier?, parameters: ParametersHolder): T
}

Error Handling

Common injection errors:

  • NoBeanDefFoundException: When requested dependency is not defined in Koin modules
  • ClosedScopeException: When trying to inject from a closed scope
  • UnknownKoinContext: When Koin context is not available in composition
  • ParameterException: When required parameters are missing or incorrect
@Composable
fun SafeInjection() {
    try {
        val service: MyService = koinInject()
        // Use service
    } catch (e: Exception) {
        // Handle injection failure
        Text("Service unavailable: ${e.message}")
    }
}

Install with Tessl CLI

npx tessl i tessl/maven-io-insert-koin--koin-compose

docs

application-setup.md

context-access.md

dependency-injection.md

index.md

module-management.md

scope-management.md

tile.json