Koin dependency injection integration with Jetpack Compose for Kotlin Multiplatform development.
—
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.
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()
}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()
}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()
}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()
}
}
}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)
}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)
}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)
}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
}
}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()
}
}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)
}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
}
}
}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()
}
}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
}Special modules for Compose previews:
@Preview
@Composable
fun ComponentPreview() {
rememberKoinModules {
listOf(previewModule)
}
ComponentContent()
}
val previewModule = module {
single<Repository> { MockRepository() }
single<ApiService> { PreviewApiService() }
}unloadModules = true for temporary featuresInstall with Tessl CLI
npx tessl i tessl/maven-io-insert-koin--koin-compose-jvm