CtrlK
BlogDocsLog inGet started
Tessl Logo

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

Koin dependency injection integration with Jetpack Compose for Kotlin Multiplatform development.

Pending
Overview
Eval results
Files

dependency-injection.mddocs/

Dependency Injection

Type-safe dependency resolution functions that integrate seamlessly with Compose recomposition, supporting parameters, qualifiers, and custom scopes for flexible dependency injection patterns.

Capabilities

Basic Dependency Injection

Resolve Koin dependency without parameters using reified generics for type safety.

/**
 * Resolve Koin dependency without parameters
 * @param qualifier Dependency qualifier (optional)
 * @param scope Koin scope (default: current scope)
 * @return Instance of type T
 */
@Composable
inline fun <reified T> koinInject(
    qualifier: Qualifier? = null,
    scope: Scope = currentKoinScope()
): T

Usage Examples:

@Composable
fun UserScreen() {
    // Basic injection
    val repository: UserRepository = koinInject()
    val apiService: ApiService = koinInject()
    
    // With qualifier
    val cacheRepo: UserRepository = koinInject(qualifier = named("cache"))
    val networkRepo: UserRepository = koinInject(qualifier = named("network"))
    
    // With custom scope
    val sessionService: SessionService = koinInject(scope = getKoin().getScope("session"))
    
    LaunchedEffect(Unit) {
        val users = repository.getAllUsers()
        // Use injected dependencies
    }
}

Dependency Injection with Parameters (Lambda)

Resolve Koin dependency with parameters using a lambda function. Note that parameters are unwrapped on recomposition, which may impact performance.

/**
 * Resolve Koin dependency with parameters (unwraps to ParametersHolder on recomposition)
 * @param qualifier Dependency qualifier (optional)
 * @param scope Koin scope (default: current scope)
 * @param parameters Injected parameters with lambda
 * @return Instance of type T
 * @note Parameters unwrapped on recomposition - use ParametersHolder version for better performance
 */
@Composable
inline fun <reified T> koinInject(
    qualifier: Qualifier? = null,
    scope: Scope = currentKoinScope(),
    noinline parameters: ParametersDefinition
): T

Usage Examples:

@Composable
fun DetailScreen(userId: String) {
    // Inject with parameters - unwrapped on each recomposition
    val userService: UserService = koinInject { parametersOf(userId) }
    val userProfile: UserProfile = koinInject(
        qualifier = named("detailed")
    ) { parametersOf(userId, true) }
    
    // Use the injected services
    LaunchedEffect(userId) {
        val user = userService.getUser()
        // ...
    }
}

Dependency Injection with ParametersHolder

Resolve Koin dependency with ParametersHolder for better performance by avoiding parameter unwrapping on recomposition.

/**
 * Resolve Koin dependency with ParametersHolder (better performance)
 * @param qualifier Dependency qualifier (optional)
 * @param scope Koin scope (default: current scope)
 * @param parametersHolder Parameters (use 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:

@Composable
fun OptimizedDetailScreen(userId: String) {
    // Better performance - parameters not unwrapped on recomposition
    val userParams = remember(userId) { parametersOf(userId) }
    val userService: UserService = koinInject(parametersHolder = userParams)
    
    val detailedParams = remember(userId) { parametersOf(userId, true) }
    val userProfile: UserProfile = koinInject(
        qualifier = named("detailed"),
        parametersHolder = detailedParams
    )
    
    // Use the injected services
}

Performance Considerations

Parameter Handling

When injecting dependencies with parameters, choose the appropriate method based on performance requirements:

  • Lambda parameters: Convenient but unwrapped on every recomposition
  • ParametersHolder: Better performance, especially for frequently recomposing components
@Composable
fun PerformanceExample(id: String) {
    // ❌ Poor performance - unwrapped on every recomposition
    val service1: MyService = koinInject { parametersOf(id) }
    
    // ✅ Better performance - parameters remembered
    val params = remember(id) { parametersOf(id) }
    val service2: MyService = koinInject(parametersHolder = params)
}

Scope Management

Inject from appropriate scopes to optimize memory usage and lifecycle management:

@Composable
fun ScopeExample() {
    // Application-level singleton
    val appService: AppService = koinInject()
    
    // Session-scoped instance
    val sessionScope = getKoin().getScope("session")
    val sessionService: SessionService = koinInject(scope = sessionScope)
    
    // Feature-scoped instance  
    val featureScope = getKoin().getScope("feature")
    val featureService: FeatureService = koinInject(scope = featureScope)
}

Type Safety and Generics

The injection functions use reified generics to maintain full type safety:

@Composable
fun TypeSafetyExample() {
    // Type inferred automatically
    val stringRepo: Repository<String> = koinInject()
    val userRepo: Repository<User> = koinInject()
    
    // With qualifiers for different implementations
    val cacheRepo: Repository<User> = koinInject(qualifier = named("cache"))
    val dbRepo: Repository<User> = koinInject(qualifier = named("database"))
}

Error Handling

Dependency injection functions will throw exceptions if dependencies cannot be resolved:

  • NoBeanDefFoundException: When the requested dependency is not defined
  • BeanCreationException: When there's an error creating the bean instance
  • UnknownKoinContext: When Koin context is not properly initialized
@Composable
fun SafeInjection() {
    try {
        val service: MyService = koinInject()
        // Use service
    } catch (e: NoBeanDefFoundException) {
        // Handle missing dependency
    } catch (e: BeanCreationException) {
        // Handle creation error
    }
}

Integration with Compose

State and Effects

Injected dependencies work seamlessly with Compose state and effects:

@Composable
fun IntegrationExample() {
    val repository: DataRepository = koinInject()
    var data by remember { mutableStateOf<List<Data>?>(null) }
    var loading by remember { mutableStateOf(false) }
    
    LaunchedEffect(repository) {
        loading = true
        try {
            data = repository.loadData()
        } finally {
            loading = false
        }
    }
    
    // UI based on injected data
    if (loading) {
        CircularProgressIndicator()
    } else {
        LazyColumn {
            items(data.orEmpty()) { item ->
                DataItem(item)
            }
        }
    }
}

ViewModels and State Holders

Inject ViewModels and other state holders for MVVM patterns:

@Composable
fun ViewModelExample() {
    val viewModel: UserViewModel = koinInject()
    val uiState by viewModel.uiState.collectAsState()
    
    LaunchedEffect(Unit) {
        viewModel.loadUsers()
    }
    
    UserList(
        users = uiState.users,
        onUserClick = viewModel::selectUser
    )
}

Best Practices

  1. Use ParametersHolder for Performance: When injecting with parameters in frequently recomposing components
  2. Remember Parameters: Always wrap parametersOf() calls in remember with appropriate keys
  3. Scope Appropriately: Inject from the most appropriate scope for your use case
  4. Handle Errors: Implement proper error handling for dependency resolution failures
  5. Type Safety: Leverage reified generics for compile-time type checking
  6. Qualifier Naming: Use descriptive qualifiers to distinguish between different implementations

Install with Tessl CLI

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

docs

application-setup.md

context-access.md

dependency-injection.md

index.md

module-management.md

scope-management.md

tile.json