Koin dependency injection integration with Jetpack Compose for Kotlin Multiplatform development.
—
Experimental APIs for creating and managing Koin scopes with automatic lifecycle management tied to Compose recomposition, enabling fine-grained dependency scoping within Compose hierarchies.
Create Koin Scope using a lambda definition and automatically close it when the Composition is forgotten or abandoned.
/**
* Create Koin Scope and close it when Composition is forgotten/abandoned
* @param scopeDefinition Lambda to define scope
* @param content Composable content
*/
@KoinExperimentalAPI
@Composable
fun KoinScope(
scopeDefinition: Koin.() -> Scope,
content: @Composable () -> Unit
)Usage Examples:
@Composable
fun FeatureWithCustomScope() {
KoinScope(
scopeDefinition = {
createScope("feature-scope", named("feature"))
}
) {
// Content has access to feature-scoped dependencies
FeatureContent()
}
}
@Composable
fun DynamicScope(userId: String) {
KoinScope(
scopeDefinition = {
createScope("user-$userId", named("user")) {
// Scope parameters can be provided
parametersOf(userId)
}
}
) {
UserProfile(userId)
}
}Create typed Koin Scope with automatic lifecycle management using a scope identifier.
/**
* Create typed Koin Scope with automatic lifecycle management
* @param T Scope type
* @param scopeID Scope identifier
* @param content Composable content
*/
@KoinExperimentalAPI
@Composable
inline fun <reified T : Any> KoinScope(
scopeID: ScopeID,
noinline content: @Composable () -> Unit
)Usage Examples:
// Define scope type
class UserSessionScope
@Composable
fun UserSession(sessionId: String) {
KoinScope<UserSessionScope>(scopeID = sessionId) {
// Content within user session scope
SessionContent()
}
}
@Composable
fun TypedFeatureScope() {
KoinScope<FeatureScope>(scopeID = "main-feature") {
FeatureComponents()
}
}Create Koin Scope with a qualifier and automatic lifecycle management.
/**
* Create Koin Scope with qualifier and automatic lifecycle management
* @param scopeID Scope identifier
* @param scopeQualifier Scope qualifier
* @param content Composable content
*/
@KoinExperimentalAPI
@Composable
fun KoinScope(
scopeID: ScopeID,
scopeQualifier: Qualifier,
content: @Composable () -> Unit
)Usage Examples:
@Composable
fun QualifiedScope(featureId: String) {
KoinScope(
scopeID = featureId,
scopeQualifier = named("feature-scope")
) {
// Content with qualified scope access
QualifiedFeatureContent()
}
}
@Composable
fun MultiLevelScope() {
KoinScope(
scopeID = "level1",
scopeQualifier = named("primary")
) {
KoinScope(
scopeID = "level2",
scopeQualifier = named("secondary")
) {
NestedScopeContent()
}
}
}Remember Koin Scope with automatic closure handling when the composable is disposed.
/**
* Remember Koin Scope with automatic closure handling
* @param scope Koin scope to remember
* @return The same scope with lifecycle management
*/
@KoinExperimentalAPI
@Composable
fun rememberKoinScope(scope: Scope): ScopeUsage Examples:
@Composable
fun RememberedScope() {
val koin = getKoin()
val customScope = rememberKoinScope(
koin.createScope("custom", named("feature"))
)
// Use the remembered scope
val scopedService: ScopedService = koinInject(scope = customScope)
// Scope will be automatically closed when component is disposed
}
@Composable
fun ConditionalScope(condition: Boolean) {
val koin = getKoin()
if (condition) {
val conditionalScope = rememberKoinScope(
koin.createScope("conditional", named("temp"))
)
ConditionalContent(scope = conditionalScope)
}
}All scope management functions automatically handle scope lifecycle:
@Composable
fun LifecycleExample() {
// Scope created when composable enters composition
KoinScope(
scopeDefinition = { createScope("auto-cleanup", named("temp")) }
) {
// Scope available here
LaunchedEffect(Unit) {
println("Scope created and available")
}
DisposableEffect(Unit) {
onDispose {
// Scope automatically closed here
println("Scope will be closed")
}
}
ScopedContent()
}
// Scope closed when composable leaves composition
}For advanced use cases, combine with manual scope management:
@Composable
fun ManualScopeControl() {
val koin = getKoin()
var manualScope by remember { mutableStateOf<Scope?>(null) }
// Create scope manually
Button(
onClick = {
manualScope = koin.createScope("manual", named("controlled"))
}
) {
Text("Create Scope")
}
// Use remembered scope when available
manualScope?.let { scope ->
val rememberedScope = rememberKoinScope(scope)
ScopedContent(scope = rememberedScope)
Button(
onClick = {
scope.close()
manualScope = null
}
) {
Text("Close Scope")
}
}
}Create hierarchical scope structures:
@Composable
fun NestedScopes() {
KoinScope(
scopeID = "app-feature",
scopeQualifier = named("feature")
) {
// Parent scope content
ParentScopeContent()
KoinScope(
scopeID = "sub-feature",
scopeQualifier = named("sub-feature")
) {
// Child scope content with access to parent scope
ChildScopeContent()
}
}
}Share data between scopes:
@Composable
fun ScopeCommunication() {
KoinScope(
scopeDefinition = {
createScope("publisher", named("data-source"))
}
) {
val publisher: DataPublisher = koinInject()
KoinScope(
scopeDefinition = {
createScope("subscriber", named("data-consumer"))
}
) {
val subscriber: DataSubscriber = koinInject()
LaunchedEffect(publisher, subscriber) {
publisher.dataFlow.collect { data ->
subscriber.process(data)
}
}
ConsumerContent()
}
}
}Create state that's tied to scope lifecycle:
@Composable
fun ScopeBasedState() {
KoinScope(
scopeID = "stateful",
scopeQualifier = named("state-scope")
) {
val stateManager: StateManager = koinInject()
var state by remember { mutableStateOf(stateManager.initialState) }
LaunchedEffect(stateManager) {
stateManager.stateFlow.collect { newState ->
state = newState
}
}
StatefulContent(
state = state,
onStateChange = stateManager::updateState
)
}
}React to scope changes:
@Composable
fun ReactiveScopeUpdates(scopeConfig: ScopeConfig) {
KoinScope(
scopeDefinition = {
createScope(scopeConfig.id, scopeConfig.qualifier)
}
) {
val currentScope = currentKoinScope()
LaunchedEffect(currentScope.id) {
println("Scope changed to: ${currentScope.id}")
}
ScopeAwareContent(scopeId = currentScope.id)
}
}Handle scope creation and lifecycle errors:
@Composable
fun SafeScopeCreation() {
var scopeError by remember { mutableStateOf<String?>(null) }
scopeError?.let { error ->
ErrorDisplay(error) {
scopeError = null
}
}
try {
KoinScope(
scopeDefinition = {
// Scope creation might fail
createScope("risky-scope", named("risky"))
}
) {
SafeScopeContent()
}
} catch (e: Exception) {
LaunchedEffect(e) {
scopeError = "Scope creation failed: ${e.message}"
}
}
}Handle missing scoped dependencies:
@Composable
fun SafeScopedInjection() {
KoinScope(
scopeID = "feature",
scopeQualifier = named("feature-scope")
) {
try {
val service: ScopedService = koinInject()
ServiceContent(service)
} catch (e: NoBeanDefFoundException) {
MissingDependencyFallback()
}
}
}Reuse scopes when possible to avoid creation overhead:
@Composable
fun EfficientScopeUsage(featureId: String) {
// Scope recreated only when featureId changes
KoinScope(
scopeDefinition = {
createScope("feature-$featureId", named("feature"))
}
) {
FeatureContent(featureId)
}
}Create scopes only when needed:
@Composable
fun ConditionalScopeCreation(needsScope: Boolean) {
if (needsScope) {
KoinScope(
scopeID = "conditional",
scopeQualifier = named("when-needed")
) {
ScopedContent()
}
} else {
UnscopedContent()
}
}Install with Tessl CLI
npx tessl i tessl/maven-io-insert-koin--koin-compose-jvm