Jetpack Compose integration for Koin dependency injection framework providing Compose-specific APIs for dependency injection
—
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.
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()
}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)
}
}@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)
}
}
}@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
)
}
}
}@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))
}
}
}
}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")
}
}
}@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)
}@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
@Composable
fun ComponentPreview() {
rememberKoinModules {
listOf(previewModule)
}
MyComponent()
}
val previewModule = module {
single<DataService> { PreviewDataService() }
single<ImageLoader> { MockImageLoader() }
}@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
}
}@Composable
fun LazyModuleLoading() {
// Lazy creation of expensive modules
rememberKoinModules(unloadModules = true) {
listOf(
// Only create when accessed
module {
single<HeavyService> { HeavyServiceImpl() }
}
)
}
}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)
}
}// 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>)
}@Composable
fun SafeModuleLoading() {
try {
rememberKoinModules(unloadModules = true) {
listOf(
riskyModule,
dependentModule
)
}
ModuleContent()
} catch (e: ModuleCreationException) {
Text("Failed to load modules: ${e.message}")
}
}// 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() }
}// ✅ 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
}// ✅ 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