Dynamic Modules in Compose
Koin provides APIs to dynamically load and unload modules tied to Composable lifecycle. This is useful for feature modules, lazy loading, and on-demand dependencies.
rememberKoinModules
Load Koin modules when a Composable enters composition:
val featureModule = module {
factory<FeatureRepository>()
viewModel<FeatureViewModel>()
}
@Composable
fun FeatureScreen() {
// Load module when this Composable enters composition
rememberKoinModules(featureModule)
val viewModel = koinViewModel<FeatureViewModel>()
}
Multiple Modules
@Composable
fun FeatureScreen() {
rememberKoinModules(
featureDataModule,
featureDomainModule,
featureUiModule
)
}
Unloading Modules
Control when modules are unloaded:
@Composable
fun FeatureScreen() {
rememberKoinModules(
featureModule,
unloadOnForgotten = true, // Unload when Composable leaves
unloadOnAbandoned = true // Unload if composition fails
)
}
| Option | When Triggered |
|---|---|
unloadOnForgotten | Composable removed from composition |
unloadOnAbandoned | Composition fails or is abandoned |
Use Cases
Feature Modules
Load feature-specific dependencies on demand:
// Feature module in separate Gradle module
val checkoutModule = module {
factory<PaymentProcessor>()
factory<CheckoutRepository>()
viewModel<CheckoutViewModel>()
}
@Composable
fun CheckoutScreen() {
rememberKoinModules(checkoutModule, unloadOnForgotten = true)
val viewModel = koinViewModel<CheckoutViewModel>()
CheckoutContent(viewModel)
}
Lazy Feature Loading
Combine with navigation for lazy feature loading:
NavHost(navController, startDestination = "home") {
composable("home") {
HomeScreen() // No extra modules needed
}
composable("checkout") {
// Checkout module loaded only when navigating here
rememberKoinModules(checkoutModule, unloadOnForgotten = true)
CheckoutScreen()
}
composable("profile") {
// Profile module loaded only when navigating here
rememberKoinModules(profileModule, unloadOnForgotten = true)
ProfileScreen()
}
}
Debug/Preview Modules
Swap implementations for previews:
val debugModule = module {
single<ApiClient> { MockApiClient() }
}
@Preview
@Composable
fun FeatureScreenPreview() {
rememberKoinModules(debugModule)
FeatureScreen()
}
Conditional Modules
Load modules based on conditions:
@Composable
fun App(isDebug: Boolean) {
if (isDebug) {
rememberKoinModules(debugModule)
}
MainScreen()
}
With Lazy Modules
Combine with Koin's lazy module loading for better performance:
val featureModule = lazyModule {
// Definitions parsed lazily when module is loaded
factory<HeavyService>()
viewModel<FeatureViewModel>()
}
@Composable
fun FeatureScreen() {
rememberKoinModules(featureModule, unloadOnForgotten = true)
val viewModel = koinViewModel<FeatureViewModel>()
}
Best Practices
-
Use
unloadOnForgotten = true- prevents memory leaksrememberKoinModules(featureModule, unloadOnForgotten = true) -
One module per feature - keep modules focused and independent
-
Combine with lazy modules - for large apps with many features
val featureModule = lazyModule { /* ... */ } -
Load at navigation level - load modules in NavHost composables
-
Avoid circular dependencies - feature modules should not depend on each other
Next Steps
- Scopes in Compose - Scope APIs
- Compose Overview - Setup and basic injection
- Isolated Context - SDK isolation