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

module-management.mddocs/

Module Management

Experimental API for dynamically loading and unloading Koin modules based on Compose lifecycle events, enabling dynamic dependency injection that responds to component composition and disposal.

Capabilities

Remember Koin Modules

Load and remember Koin modules with configurable lifecycle management, allowing modules to be automatically unloaded when components are forgotten or abandoned.

/**
 * Load and remember Koin modules with lifecycle management
 * @param unloadOnForgotten Unload modules on onForgotten event (optional)
 * @param unloadOnAbandoned Unload modules on onAbandoned event (optional)
 * @param unloadModules Unload modules on forgotten/abandoned (default: false)
 * @param modules Lambda returning list of modules to load
 */
@KoinExperimentalAPI
@Composable
inline fun rememberKoinModules(
    unloadOnForgotten: Boolean? = null,
    unloadOnAbandoned: Boolean? = null,
    unloadModules: Boolean = false,
    crossinline modules: @DisallowComposableCalls () -> List<Module> = { emptyList() }
)

Usage Examples:

@Composable
fun DynamicFeature() {
    // Basic module loading with automatic cleanup
    rememberKoinModules(unloadModules = true) {
        listOf(featureModule, networkModule)
    }
    
    // Content can now use dependencies from loaded modules
    val featureService: FeatureService = koinInject()
    
    FeatureContent(featureService)
}

@Composable
fun ConditionalModules(isPreview: Boolean) {
    rememberKoinModules(
        unloadOnForgotten = true,
        unloadOnAbandoned = true
    ) {
        if (isPreview) {
            listOf(previewModule, mockModule)
        } else {
            listOf(productionModule, realNetworkModule)
        }
    }
    
    Content()
}

Module Lifecycle Strategies

Automatic Cleanup

Modules can be automatically unloaded based on Compose RememberObserver events:

@Composable
fun AutoCleanupExample() {
    // Modules unloaded when component leaves composition
    rememberKoinModules(unloadModules = true) {
        listOf(
            temporaryModule,
            featureSpecificModule
        )
    }
    
    TemporaryFeatureContent()
}

Fine-Grained Control

Control exactly when modules are unloaded:

@Composable
fun FineGrainedControl() {
    rememberKoinModules(
        unloadOnForgotten = true,  // Unload when forgotten (recomposition skip)
        unloadOnAbandoned = false  // Keep loaded when abandoned
    ) {
        listOf(persistentModule, cacheModule)
    }
    
    // Modules persist through abandonment but unload when forgotten
    PersistentContent()
}

Manual Module Management

Combine with conditional loading for manual control:

@Composable
fun ManualModuleControl() {
    var modulesEnabled by remember { mutableStateOf(false) }
    
    if (modulesEnabled) {
        rememberKoinModules(unloadModules = true) {
            listOf(conditionalModule)
        }
    }
    
    Column {
        Switch(
            checked = modulesEnabled,
            onCheckedChange = { modulesEnabled = it }
        )
        
        if (modulesEnabled) {
            ModuleDependentContent()
        }
    }
}

Dynamic Module Loading

Conditional Module Sets

Load different modules based on runtime conditions:

@Composable
fun ConditionalModuleSets(userRole: UserRole, isDebug: Boolean) {
    rememberKoinModules(unloadModules = true) {
        buildList {
            // Base modules always loaded
            add(coreModule)
            
            // Role-based modules
            when (userRole) {
                UserRole.ADMIN -> add(adminModule)
                UserRole.USER -> add(userModule)
                UserRole.GUEST -> add(guestModule)
            }
            
            // Debug modules
            if (isDebug) {
                add(debugModule)
                add(loggingModule)
            }
        }
    }
    
    RoleBasedContent(userRole)
}

Feature-Based Loading

Load modules based on enabled features:

@Composable
fun FeatureBasedLoading(enabledFeatures: Set<Feature>) {
    rememberKoinModules(
        unloadOnForgotten = true,
        unloadOnAbandoned = false
    ) {
        enabledFeatures.mapNotNull { feature ->
            when (feature) {
                Feature.ANALYTICS -> analyticsModule
                Feature.PUSH_NOTIFICATIONS -> notificationModule
                Feature.OFFLINE_MODE -> offlineModule
                Feature.PREMIUM -> premiumModule
                else -> null
            }
        }
    }
    
    FeatureContent(enabledFeatures)
}

Integration with State

State-Driven Module Loading

React to state changes to load/unload modules:

@Composable
fun StateDrivenModules() {
    var connectionState by remember { mutableStateOf(ConnectionState.OFFLINE) }
    
    // Modules change based on connection state
    rememberKoinModules(unloadModules = true) {
        when (connectionState) {
            ConnectionState.ONLINE -> listOf(onlineModule, syncModule)
            ConnectionState.OFFLINE -> listOf(offlineModule, cacheModule)
            ConnectionState.UNKNOWN -> listOf(fallbackModule)
        }
    }
    
    LaunchedEffect(Unit) {
        networkStateFlow.collect { state ->
            connectionState = state
        }
    }
    
    ConnectionAwareContent(connectionState)
}

Navigation-Based Loading

Load modules based on navigation state:

@Composable
fun NavigationBasedModules(navController: NavController) {
    val currentDestination by navController.currentBackStackEntryAsState()
    
    rememberKoinModules(unloadModules = true) {
        when (currentDestination?.destination?.route) {
            "profile" -> listOf(profileModule, userPreferencesModule)
            "settings" -> listOf(settingsModule, configModule)
            "shop" -> listOf(shopModule, paymentModule, cartModule)
            else -> emptyList()
        }
    }
    
    NavHost(navController = navController) {
        // Navigation destinations
    }
}

Performance Considerations

Module Loading Overhead

Be mindful of module loading performance:

@Composable
fun OptimizedModuleLoading(heavyFeatureEnabled: Boolean) {
    // Only load heavy modules when actually needed
    if (heavyFeatureEnabled) {
        rememberKoinModules(unloadModules = true) {
            listOf(
                heavyComputationModule,
                largeDataModule
            )
        }
        
        HeavyFeatureContent()
    } else {
        LightweightContent()
    }
}

Module Reuse

Prevent unnecessary module reloading:

@Composable
fun EfficientModuleReuse(config: FeatureConfig) {
    // Modules only reload when config.moduleSet changes
    val moduleSet = remember(config.moduleSet) { config.moduleSet }
    
    rememberKoinModules(unloadModules = true) {
        moduleSet.map { moduleFactory ->
            moduleFactory.create()
        }
    }
    
    ConfigurableContent(config)
}

Error Handling

Module Loading Errors

Handle module loading failures gracefully:

@Composable
fun SafeModuleLoading() {
    var moduleError by remember { mutableStateOf<String?>(null) }
    
    try {
        rememberKoinModules(unloadModules = true) {
            listOf(
                riskyModule,
                dependentModule
            )
        }
        
        NormalContent()
        
    } catch (e: Exception) {
        LaunchedEffect(e) {
            moduleError = "Failed to load modules: ${e.message}"
        }
    }
    
    moduleError?.let { error ->
        ErrorMessage(error) {
            moduleError = null
        }
    }
}

Missing Module Dependencies

Handle cases where modules have missing dependencies:

@Composable
fun RobustModuleLoading() {
    var fallbackMode by remember { mutableStateOf(false) }
    
    if (!fallbackMode) {
        try {
            rememberKoinModules(unloadModules = true) {
                listOf(primaryModule, dependencyModule)
            }
            
            PrimaryContent()
            
        } catch (e: BeanCreationException) {
            LaunchedEffect(e) {
                fallbackMode = true
            }
        }
    } else {
        rememberKoinModules(unloadModules = true) {
            listOf(fallbackModule)
        }
        
        FallbackContent()
    }
}

Testing Support

Test Module Loading

Load test-specific modules in test environments:

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

// In tests
@Test
fun testComponent() {
    composeTestRule.setContent {
        TestableComponent(isTest = true)
    }
    
    // Test with mocked dependencies
}

Preview Module Loading

Special modules for Compose previews:

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

val previewModule = module {
    single<Repository> { MockRepository() }
    single<ApiService> { PreviewApiService() }
}

Best Practices

  1. Lifecycle Awareness: Use appropriate unload strategies based on your use case
  2. Performance: Load modules conditionally to avoid unnecessary overhead
  3. Error Handling: Implement fallback strategies for module loading failures
  4. State Integration: Tie module loading to relevant state changes
  5. Testing: Provide test-specific module loading strategies
  6. Memory Management: Use unloadModules = true for temporary features
  7. Module Design: Design modules to be independently loadable and unloadable
  8. Experimental API: Remember this is an experimental API and may change in future versions

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