Jetpack Compose integration for Koin dependency injection framework providing Compose-specific APIs for dependency injection
—
Advanced scope management capabilities allowing creation and management of Koin scopes with automatic lifecycle handling tied to Compose composition lifecycle. Scopes are automatically closed when compositions are forgotten or abandoned.
Create a Koin scope using a lambda definition and automatically manage its lifecycle with Compose.
/**
* Create Koin Scope & close it when Composition is on onForgotten/onAbandoned
*
* @param scopeDefinition - lambda to define scope
* @param content - composable content that uses the scope
*/
@Composable
@KoinExperimentalAPI
fun KoinScope(
scopeDefinition: Koin.() -> Scope,
content: @Composable () -> Unit
)Usage Examples:
import org.koin.compose.getKoin
import org.koin.compose.scope.KoinScope
@Composable
fun UserProfileScreen(userId: String) {
KoinScope(
scopeDefinition = {
createScope<UserProfileScope>("user-profile-$userId")
}
) {
// Content within this scope can access UserProfile-scoped dependencies
UserProfileContent()
UserSettingsPanel()
}
// Scope is automatically closed when this composition is forgotten
}
@Composable
fun SessionBasedScreen() {
val sessionId = remember { UUID.randomUUID().toString() }
KoinScope(
scopeDefinition = {
getOrCreateScope("session", StringQualifier(sessionId))
}
) {
// All composables here share the same session scope
HeaderComponent()
MainContent()
FooterComponent()
}
}Create a Koin scope from a specific type with automatic lifecycle management.
/**
* Create Koin Scope from type T & close it when Composition is on onForgotten/onAbandoned
*
* @param scopeID - unique identifier for the scope
* @param content - composable content that uses the scope
*/
@Composable
@KoinExperimentalAPI
inline fun <reified T : Any> KoinScope(
scopeID: ScopeID,
noinline content: @Composable () -> Unit
)Usage Examples:
import org.koin.compose.scope.KoinScope
// Define scope types
class UserScope
class FeatureScope
class ActivityScope
@Composable
fun UserDashboard(userId: String) {
KoinScope<UserScope>(
scopeID = "user-$userId"
) {
// This content has access to UserScope-scoped dependencies
UserHeader()
UserStats()
UserActions()
}
}
@Composable
fun FeatureScreen(featureId: String) {
KoinScope<FeatureScope>(
scopeID = "feature-$featureId"
) {
FeatureContent()
}
}Create a Koin scope with both scope ID and qualifier for more specific scope management.
/**
* Create Koin Scope from type with qualifier & close it when Composition is on onForgotten/onAbandoned
*
* @param scopeID - unique identifier for the scope
* @param scopeQualifier - qualifier for the scope
* @param content - composable content that uses the scope
*/
@Composable
@KoinExperimentalAPI
fun KoinScope(
scopeID: ScopeID,
scopeQualifier: Qualifier,
noinline content: @Composable () -> Unit
)Usage Examples:
import org.koin.compose.scope.KoinScope
import org.koin.core.qualifier.named
@Composable
fun MultiTenantScreen(tenantId: String, environment: String) {
KoinScope(
scopeID = "tenant-$tenantId",
scopeQualifier = named(environment) // "dev", "staging", "prod"
) {
// Content uses tenant-specific, environment-qualified scope
TenantHeader()
TenantContent()
}
}
@Composable
fun GameSession(gameId: String, difficulty: String) {
KoinScope(
scopeID = "game-$gameId",
scopeQualifier = named("difficulty-$difficulty")
) {
GameBoard()
GameStats()
GameControls()
}
}Remember a Koin scope and handle its lifecycle with Compose's remember system.
/**
* Remember Koin Scope & run CompositionKoinScopeLoader to handle scope closure
*
* @param scope - Koin scope to remember and manage
* @return The managed scope instance
*/
@Composable
@KoinExperimentalAPI
fun rememberKoinScope(scope: Scope): ScopeUsage Examples:
import org.koin.compose.getKoin
import org.koin.compose.scope.rememberKoinScope
@Composable
fun CustomScopeManagement(scopeId: String) {
val koin = getKoin()
// Create scope manually
val customScope = remember(scopeId) {
koin.createScope<MyCustomScope>(scopeId)
}
// Remember and manage its lifecycle
val managedScope = rememberKoinScope(customScope)
// Use the managed scope
val scopedService = remember(managedScope) {
managedScope.get<MyScopedService>()
}
MyCustomContent(scopedService)
}
@Composable
fun ConditionalScope(shouldCreateScope: Boolean) {
if (shouldCreateScope) {
val scope = remember {
getKoin().createScope<ConditionalScope>("conditional")
}
val managedScope = rememberKoinScope(scope)
ConditionalContent(managedScope)
} else {
DefaultContent()
}
}All KoinScope composables automatically close their scopes when:
// Internal lifecycle management
class CompositionKoinScopeLoader(val scope: Scope) : RememberObserver {
override fun onRemembered() {
// Scope is active
}
override fun onForgotten() {
close()
}
override fun onAbandoned() {
close()
}
private fun close() {
if (!scope.isRoot && !scope.closed) {
scope.close()
}
}
}@Composable
fun ManualScopeLifecycle() {
val scope = remember { getKoin().createScope<MyScope>("manual") }
// Manual lifecycle control
DisposableEffect(scope) {
onDispose {
if (!scope.closed) {
scope.close()
}
}
}
// Don't use rememberKoinScope if managing manually
ScopeContent(scope)
}@Composable
fun UserScopedScreen(user: User) {
KoinScope<UserScope>(
scopeID = "user-${user.id}"
) {
// All user-related components share this scope
UserProfile(user)
UserPreferences(user)
UserActivity(user)
}
}@Composable
fun FeatureModule(featureFlag: String) {
KoinScope(
scopeDefinition = {
createScope<FeatureScope>("feature-$featureFlag")
}
) {
if (featureFlag == "new_ui") {
NewUIComponents()
} else {
LegacyUIComponents()
}
}
}@Composable
fun NavigationScope(route: String) {
KoinScope(
scopeID = "nav-$route",
scopeQualifier = named("navigation")
) {
when (route) {
"home" -> HomeScreen()
"profile" -> ProfileScreen()
"settings" -> SettingsScreen()
}
}
}When inside a KoinScope, use koinInject normally - it will automatically use the current scope:
@Composable
fun UserProfileScreen(userId: String) {
KoinScope<UserScope>(scopeID = "user-$userId") {
UserProfileContent() // Can inject UserScope dependencies
}
}
@Composable
fun UserProfileContent() {
// This will inject from the UserScope
val userRepository: UserRepository = koinInject()
val userPreferences: UserPreferences = koinInject()
// Content using scoped dependencies
Text("User: ${userRepository.getCurrentUser().name}")
}@Composable
fun SafeScopeCreation(scopeId: String) {
try {
KoinScope<MyScope>(scopeID = scopeId) {
ScopeContent()
}
} catch (e: ScopeAlreadyCreatedException) {
// Handle duplicate scope creation
Text("Scope already exists: $scopeId")
} catch (e: Exception) {
// Handle other scope creation errors
Text("Failed to create scope: ${e.message}")
}
}@Composable
fun ScopeAwareContent() {
val scope = currentKoinScope()
if (scope.closed) {
Text("Scope is closed")
} else {
try {
val service: MyService = koinInject()
ServiceContent(service)
} catch (e: ClosedScopeException) {
Text("Scope was closed during access")
}
}
}// Scope types
typealias ScopeID = String
interface Scope {
val id: String
val isRoot: Boolean
val closed: Boolean
val logger: Logger
fun close()
}
// Scope management
interface RememberObserver {
fun onRemembered()
fun onForgotten()
fun onAbandoned()
}
// Qualifiers for scope identification
interface Qualifier
class StringQualifier(val value: String) : Qualifier
fun named(name: String): Qualifier// ✅ Good: Descriptive, unique scope IDs
KoinScope<UserScope>("user-${user.id}")
KoinScope<FeatureScope>("feature-shopping-cart")
KoinScope<SessionScope>("session-${sessionId}")
// ❌ Poor: Generic, collision-prone scope IDs
KoinScope<UserScope>("scope")
KoinScope<FeatureScope>("temp")// ✅ Good: Appropriate scope granularity
@Composable
fun ShoppingFlow() {
KoinScope<ShoppingScope>("shopping-session") {
ProductCatalog()
ShoppingCart()
Checkout()
}
}
// ❌ Poor: Too many nested scopes
@Composable
fun OverScopedFlow() {
KoinScope<Scope1>("scope1") {
KoinScope<Scope2>("scope2") {
KoinScope<Scope3>("scope3") {
Content() // Too much nesting
}
}
}
}// ✅ Good: Scope lifetime matches business logic
@Composable
fun UserSession(user: User) {
// Scope lives for entire user session
KoinScope<UserScope>("user-${user.id}") {
UserDashboard()
}
}
// ✅ Good: Scope per business operation
@Composable
fun CheckoutFlow(cartId: String) {
// Scope lives for checkout process
KoinScope<CheckoutScope>("checkout-$cartId") {
CheckoutSteps()
}
}Install with Tessl CLI
npx tessl i tessl/maven-io-insert-koin--koin-compose