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

module-management.mddocs/

Module Management

Dynamic module loading and unloading capabilities with integration into Compose lifecycle for automatic cleanup. Enables loading Koin modules on-demand during composition and automatically unloading them when no longer needed.

Capabilities

rememberKoinModules

Load and remember Koin modules with automatic lifecycle management tied to Compose's remember system.

/**
 * Load and remember Modules & run CompositionKoinModuleLoader to handle scope closure
 *
 * @param unloadOnForgotten : unload loaded modules on onForgotten event
 * @param unloadOnAbandoned : unload loaded modules on onAbandoned event  
 * @param unloadModules : unload loaded modules on onForgotten or onAbandoned event
 * @param modules : lambda providing list of modules to load
 */
@Composable
@KoinExperimentalAPI
inline fun rememberKoinModules(
    unloadOnForgotten: Boolean? = null,
    unloadOnAbandoned: Boolean? = null,
    unloadModules: Boolean = false,
    crossinline modules: @DisallowComposableCalls () -> List<Module> = { emptyList() }
)

Usage Examples:

import org.koin.compose.module.rememberKoinModules
import org.koin.dsl.module

@Composable
fun FeatureScreen(featureEnabled: Boolean) {
    if (featureEnabled) {
        // Load feature-specific modules when feature is enabled
        rememberKoinModules(
            unloadModules = true // Unload when composition is forgotten/abandoned
        ) {
            listOf(
                module {
                    single<FeatureService> { FeatureServiceImpl() }
                    single<FeatureRepository> { FeatureRepositoryImpl() }
                },
                module {
                    single<FeatureAnalytics> { FeatureAnalyticsImpl() }
                }
            )
        }
        
        FeatureContent()
    } else {
        DefaultContent()
    }
}

@Composable
fun DynamicModules(userId: String?) {
    // Load user-specific modules only when user is logged in
    userId?.let { id ->
        rememberKoinModules(
            unloadOnForgotten = true,
            unloadOnAbandoned = false
        ) {
            listOf(
                createUserModule(id),
                createUserPreferencesModule(id)
            )
        }
    }
    
    UserInterface()
}

Module Lifecycle Control

Fine-grained control over when modules are unloaded:

@Composable
fun ModuleLifecycleDemo() {
    // Never unload automatically
    rememberKoinModules {
        listOf(persistentModule)
    }
    
    // Unload only when forgotten (e.g., navigation away)
    rememberKoinModules(
        unloadOnForgotten = true,
        unloadOnAbandoned = false
    ) {
        listOf(navigationScopedModule)
    }
    
    // Unload only when abandoned (e.g., configuration change)
    rememberKoinModules(
        unloadOnForgotten = false,
        unloadOnAbandoned = true
    ) {
        listOf(configurationSensitiveModule)
    }
    
    // Unload in both scenarios (shorthand)
    rememberKoinModules(unloadModules = true) {
        listOf(temporaryModule)
    }
}

Advanced Usage Patterns

Conditional Module Loading

@Composable
fun ConditionalModules(
    isDebugMode: Boolean,
    featureFlags: Set<String>,
    userTier: UserTier
) {
    // Debug modules only in debug mode
    if (isDebugMode) {
        rememberKoinModules(unloadModules = true) {
            listOf(debugModule, loggingModule)
        }
    }
    
    // Feature-specific modules based on flags
    rememberKoinModules(unloadModules = true) {
        featureFlags.mapNotNull { flag ->
            when (flag) {
                "new_ui" -> newUIModule
                "analytics" -> analyticsModule
                "experimental" -> experimentalModule
                else -> null
            }
        }
    }
    
    // Tier-based modules
    rememberKoinModules(unloadModules = true) {
        when (userTier) {
            UserTier.FREE -> listOf(freeUserModule)
            UserTier.PREMIUM -> listOf(freeUserModule, premiumModule)
            UserTier.ENTERPRISE -> listOf(freeUserModule, premiumModule, enterpriseModule)
        }
    }
}

Environment-Based Module Loading

@Composable
fun EnvironmentModules(environment: Environment) {
    rememberKoinModules(unloadModules = true) {
        when (environment) {
            Environment.DEVELOPMENT -> listOf(
                devApiModule,
                mockDataModule,
                debugToolsModule
            )
            Environment.STAGING -> listOf(
                stagingApiModule,
                realDataModule,
                limitedDebugModule
            )
            Environment.PRODUCTION -> listOf(
                prodApiModule,
                realDataModule,
                analyticsModule
            )
        }
    }
}

Dynamic User Context Modules

@Composable
fun UserContextModules(user: User?) {
    // Base modules always loaded
    rememberKoinModules {
        listOf(baseAppModule, authModule)
    }
    
    // User-specific modules loaded when user is available
    user?.let { currentUser ->
        rememberKoinModules(unloadModules = true) {
            buildList {
                // User data module
                add(createUserDataModule(currentUser.id))
                
                // Role-based modules
                if (currentUser.isAdmin) {
                    add(adminModule)
                }
                
                if (currentUser.hasPermission("analytics")) {
                    add(userAnalyticsModule)
                }
                
                // Locale-specific modules
                add(createLocaleModule(currentUser.locale))
            }
        }
    }
}

Module Creation Helpers

Dynamic Module Creation

fun createUserModule(userId: String) = module {
    single<UserRepository> { UserRepositoryImpl(userId) }
    single<UserPreferences> { UserPreferencesImpl(userId) }
    single<UserCache> { UserCacheImpl(userId) }
}

fun createFeatureModule(featureConfig: FeatureConfig) = module {
    single { featureConfig }
    single<FeatureService> { 
        if (featureConfig.useV2) {
            FeatureServiceV2Impl(get())
        } else {
            FeatureServiceV1Impl(get())
        }
    }
}

fun createEnvironmentModule(env: String) = module {
    single<ApiClient> { 
        when (env) {
            "dev" -> DevApiClient()
            "staging" -> StagingApiClient()
            "prod" -> ProdApiClient()
            else -> throw IllegalArgumentException("Unknown environment: $env")
        }
    }
}

Module Composition

@Composable
fun CompositeModules(config: AppConfig) {
    rememberKoinModules(unloadModules = true) {
        buildList {
            // Core modules always included
            addAll(coreModules)
            
            // Add platform-specific modules
            addAll(getPlatformModules())
            
            // Add feature modules based on config
            if (config.enableAnalytics) {
                add(analyticsModule)
            }
            
            if (config.enableCrashReporting) {
                add(crashReportingModule)
            }
            
            // Add theme modules
            add(getThemeModule(config.theme))
        }
    }
}

private fun coreModules() = listOf(
    networkModule,
    databaseModule,
    repositoryModule
)

private fun getPlatformModules() = when (Platform.current) {
    Platform.Android -> listOf(androidModule)
    Platform.iOS -> listOf(iosModule)
    Platform.Desktop -> listOf(desktopModule)
}

Testing with Module Management

Test Module Injection

@Composable
fun TestableComponent(isTestMode: Boolean = false) {
    if (isTestMode) {
        rememberKoinModules(unloadModules = true) {
            listOf(
                testModule,
                mockModule
            )
        }
    } else {
        rememberKoinModules(unloadModules = true) {
            listOf(
                productionModule,
                realModule
            )
        }
    }
    
    ComponentContent()
}

// Test modules
val testModule = module {
    single<ApiService> { MockApiService() }
    single<Database> { InMemoryDatabase() }
}

val mockModule = module {
    single<UserRepository> { MockUserRepository() }
    single<AnalyticsService> { NoOpAnalyticsService() }
}

Preview Module Support

@Preview
@Composable
fun ComponentPreview() {
    rememberKoinModules {
        listOf(previewModule)
    }
    
    MyComponent()
}

val previewModule = module {
    single<DataService> { PreviewDataService() }
    single<ImageLoader> { MockImageLoader() }
}

Performance Considerations

Module Loading Performance

@Composable
fun OptimizedModuleLoading(heavyFeatureEnabled: Boolean) {
    // Use remember to avoid recreating modules on recomposition
    val dynamicModules = remember(heavyFeatureEnabled) {
        if (heavyFeatureEnabled) {
            listOf(heavyFeatureModule, additionalModule)
        } else {
            emptyList()
        }
    }
    
    rememberKoinModules(unloadModules = true) {
        dynamicModules
    }
}

Lazy Module Creation

@Composable
fun LazyModuleLoading() {
    // Lazy creation of expensive modules
    rememberKoinModules(unloadModules = true) {
        listOf(
            // Only create when accessed
            module {
                single<HeavyService> { HeavyServiceImpl() }
            }
        )
    }
}

Internal Implementation

CompositionKoinModuleLoader

The internal class that manages module lifecycle:

@KoinExperimentalAPI
@KoinInternalApi
class CompositionKoinModuleLoader(
    val modules: List<Module>,
    val koin: Koin,
    val unloadOnForgotten: Boolean,
    val unloadOnAbandoned: Boolean
) : RememberObserver {
    
    init {
        koin.loadModules(modules)
    }
    
    override fun onRemembered() {
        // Module loading happens in init
    }
    
    override fun onForgotten() {
        if (unloadOnForgotten) {
            unloadModules()
        }
    }
    
    override fun onAbandoned() {
        if (unloadOnAbandoned) {
            unloadModules()
        }
    }
    
    private fun unloadModules() {
        koin.unloadModules(modules)
    }
}

Core Types

// Module types
interface Module {
    val id: String
    val createdAtStart: Boolean
}

// Module lifecycle
interface RememberObserver {
    fun onRemembered()
    fun onForgotten() 
    fun onAbandoned()
}

// Koin instance for module management
interface Koin {
    fun loadModules(modules: List<Module>)
    fun unloadModules(modules: List<Module>)
}

Error Handling

Module Loading Failures

@Composable
fun SafeModuleLoading() {
    try {
        rememberKoinModules(unloadModules = true) {
            listOf(
                riskyModule,
                dependentModule
            )
        }
        
        ModuleContent()
    } catch (e: ModuleCreationException) {
        Text("Failed to load modules: ${e.message}")
    }
}

Circular Dependencies

// Avoid circular dependencies in dynamic modules
val moduleA = module {
    single<ServiceA> { ServiceAImpl(get<ServiceB>()) }
}

val moduleB = module {
    single<ServiceB> { ServiceBImpl(get<ServiceA>()) } // ❌ Circular dependency
}

// Better approach: Use interfaces or break the cycle
val moduleA = module {
    single<ServiceA> { ServiceAImpl(get<ServiceBInterface>()) }
}

val moduleB = module {
    single<ServiceBInterface> { ServiceBImpl() }
}

Best Practices

Module Organization

// ✅ Good: Organize modules by feature/layer
val networkModule = module { /* network dependencies */ }
val dataModule = module { /* data layer dependencies */ }
val userFeatureModule = module { /* user feature dependencies */ }

// ❌ Poor: Monolithic modules
val everythingModule = module { 
    // Too many unrelated dependencies
}

Lifecycle Management

// ✅ Good: Match module lifecycle to business logic
@Composable
fun UserSession() {
    // Load user modules for entire session
    rememberKoinModules(unloadOnForgotten = true) {
        listOf(userSessionModule)
    }
}

// ✅ Good: Temporary modules for short-lived features
@Composable
fun OnboardingFlow() {
    rememberKoinModules(unloadModules = true) {
        listOf(onboardingModule) // Unload when onboarding ends
    }
}

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