Koin Core is a pragmatic lightweight dependency injection framework for Kotlin Multiplatform with iOS Simulator ARM64 target support.
—
This document covers Koin's scoping system, which provides lifecycle management for dependencies by grouping related instances that should be created and destroyed together.
Scopes in Koin enable you to:
A scope represents a bounded context where instances live and die together, making it perfect for features like user sessions, request handling, or component lifecycles.
The Scope class provides the container for scoped instances and resolution methods:
class Scope(
val scopeQualifier: Qualifier,
val id: ScopeID,
val isRoot: Boolean = false,
val scopeArchetype: TypeQualifier? = null,
@PublishedApi
internal val _koin: Koin
) : Lockable() {
val closed: Boolean
// Resolution methods
inline fun <reified T : Any> inject(
qualifier: Qualifier? = null,
mode: LazyThreadSafetyMode = LazyThreadSafetyMode.SYNCHRONIZED,
noinline parameters: ParametersDefinition? = null
): Lazy<T>
inline fun <reified T : Any> get(
qualifier: Qualifier? = null,
noinline parameters: ParametersDefinition? = null
): T
inline fun <reified T : Any> getOrNull(/* ... */): T?
inline fun <reified T : Any> injectOrNull(/* ... */): Lazy<T?>
fun <T> get(clazz: KClass<*>, qualifier: Qualifier?, parameters: ParametersDefinition?): T
fun <T> getOrNull(clazz: KClass<*>, qualifier: Qualifier?, parameters: ParametersDefinition?): T?
inline fun <reified T : Any> getAll(): List<T>
// Scope operations
fun linkTo(vararg scopes: Scope)
fun unlink(vararg scopes: Scope)
fun close()
// Instance declaration
inline fun <reified T> declare(
instance: T,
qualifier: Qualifier? = null,
secondaryTypes: List<KClass<*>> = emptyList(),
allowOverride: Boolean = true,
holdInstance: Boolean = false
)
// Scope access
fun getKoin(): Koin
fun getScope(scopeID: ScopeID): Scope
// Callbacks
fun registerCallback(callback: ScopeCallback)
// Properties (inherited from Koin)
fun <T : Any> getProperty(key: String, defaultValue: T): T
fun <T : Any> getPropertyOrNull(key: String): T?
fun <T : Any> getProperty(key: String): T
}
typealias ScopeID = Stringimport org.koin.core.qualifier.named
val koin = koinApplication { modules(myModule) }.koin
// Create scope with qualifier and ID
val userScope = koin.createScope("user-123", named("user"))
// Create scope with type-based qualifier
val sessionScope = koin.createScope<UserSession>("session-456")
// Create scope with auto-generated ID
val tempScope = koin.createScope<TempData>()// In Koin class
fun createScope(scopeId: ScopeID, qualifier: Qualifier, source: Any? = null, scopeArchetype: TypeQualifier? = null): Scope
inline fun <reified T : Any> createScope(scopeId: ScopeID, source: Any? = null, scopeArchetype: TypeQualifier? = null): Scope
inline fun <reified T : Any> createScope(scopeId: ScopeID = KoinPlatformTools.generateId()): Scope
fun <T : KoinScopeComponent> createScope(t: T): Scope
// Get or create patterns
fun getOrCreateScope(scopeId: ScopeID, qualifier: Qualifier, source: Any? = null): Scope
inline fun <reified T : Any> getOrCreateScope(scopeId: ScopeID): Scopeclass UserSession(val userId: String)
class ShoppingCart(val sessionId: String)
// User session scope
val userSession = UserSession("user-123")
val sessionScope = koin.createScope<UserSession>("session-${userSession.userId}")
// Shopping cart scope
val cartScope = koin.createScope("cart-abc", named("shopping"))
// Get or create - won't create duplicate if exists
val existingScope = koin.getOrCreateScope<UserSession>("session-user-123")class ScopeDSL(val scopeQualifier: Qualifier, val module: Module) {
inline fun <reified T> scoped(
qualifier: Qualifier? = null,
noinline definition: Definition<T>
): KoinDefinition<T>
inline fun <reified T> factory(
qualifier: Qualifier? = null,
noinline definition: Definition<T>
): KoinDefinition<T>
}import org.koin.dsl.*
import org.koin.core.qualifier.named
val userModule = module {
// Define a scope for user sessions
scope(named("user")) {
scoped<UserPreferences> { UserPreferences() }
scoped<UserCache> { UserCache() }
factory<UserActivity> { UserActivity() } // New instance each time
}
}class UserSession
class PaymentFlow
val scopedModule = module {
// Type-based scope definition
scope<UserSession> {
scoped<SessionData> { SessionData() }
scoped<AuthToken> { AuthToken() }
}
scope<PaymentFlow> {
scoped<PaymentData> { PaymentData() }
scoped<TransactionLog> { TransactionLog() }
}
}val advancedScopeModule = module {
// Global singletons
single<Database> { Database() }
single<Logger> { Logger() }
scope<UserSession> {
// Scoped instances can inject global singletons
scoped<UserRepository> { UserRepository(get()) } // Injects Database
// Scoped instances can inject other scoped instances
scoped<UserService> {
UserService(get<UserRepository>(), get<Logger>())
}
// Factory within scope
factory<UserQuery> { params ->
val queryType: String = params.get()
UserQuery(queryType, get<UserRepository>())
}
}
}// Create scope instance
val userScope = koin.createScope<UserSession>("user-123")
// Resolve scoped dependencies
val userService: UserService = userScope.get()
val userRepo: UserRepository = userScope.get()
// Lazy resolution
val userCache: Lazy<UserCache> = userScope.inject()
// With parameters
val userQuery: UserQuery = userScope.get { parametersOf("findByEmail") }
// Nullable resolution
val optionalService: OptionalService? = userScope.getOrNull()// Create and use scope
val scope = koin.createScope<UserSession>("user-123")
// All scoped instances are created as needed
val service1 = scope.get<UserService>() // Creates UserService + dependencies
val service2 = scope.get<UserService>() // Returns same UserService instance
// Close scope - destroys all scoped instances
scope.close()
// Attempting to use closed scope throws exception
// val service3 = scope.get<UserService>() // ClosedScopeException!Create hierarchical relationships between scopes:
// Parent scope
val applicationScope = koin.createScope<Application>("app")
// Child scope linked to parent
val userScope = koin.createScope<UserSession>("user-123")
userScope.linkTo(applicationScope)
// Child can resolve from parent scope
val appConfig: AppConfig = userScope.get() // Resolves from applicationScope
// Multiple links
val requestScope = koin.createScope<RequestContext>("request-456")
requestScope.linkTo(userScope, applicationScope) // Links to both
// Unlinking
requestScope.unlink(applicationScope)Register callbacks for scope lifecycle events:
import org.koin.core.scope.ScopeCallback
class UserSessionCallback : ScopeCallback {
override fun onScopeClose(scope: Scope) {
println("User session ${scope.id} is closing")
// Cleanup user-specific resources
}
}
val userScope = koin.createScope<UserSession>("user-123")
userScope.registerCallback(UserSessionCallback())
// When scope closes, callback is invoked
userScope.close() // Prints: "User session user-123 is closing"Declare instances directly in scopes at runtime:
val userScope = koin.createScope<UserSession>("user-123")
// Declare instance in scope
val sessionData = SessionData("user-123", "premium")
userScope.declare(sessionData)
// Now available for resolution
val data: SessionData = userScope.get()// Declare with qualifier
userScope.declare(
instance = UserPreferences("dark-mode"),
qualifier = named("ui")
)
// Declare with multiple type bindings
interface Cache
class UserCache : Cache
userScope.declare(
instance = UserCache(),
secondaryTypes = listOf(Cache::class)
)
// Can resolve as either type
val cache: Cache = userScope.get()
val userCache: UserCache = userScope.get()// Instance held in scope (default)
userScope.declare(
instance = PersistentData(),
holdInstance = true // Available in this and future scope instances
)
// Instance not held in scope
userScope.declare(
instance = TemporaryData(),
holdInstance = false // Only available in current scope instance
)class UserSessionManager(private val koin: Koin) {
private val userScopes = mutableMapOf<String, Scope>()
fun startUserSession(userId: String): Scope {
val scope = koin.createScope<UserSession>("user-$userId")
// Initialize session-specific data
scope.declare(UserSessionData(userId))
scope.declare(UserPreferences.load(userId))
userScopes[userId] = scope
return scope
}
fun endUserSession(userId: String) {
userScopes[userId]?.close()
userScopes.remove(userId)
}
fun getUserScope(userId: String): Scope? {
return userScopes[userId]
}
}class RequestProcessor(private val koin: Koin) {
fun processRequest(requestId: String): Response {
val requestScope = koin.createScope<RequestContext>(requestId)
try {
// Declare request-specific data
requestScope.declare(RequestData(requestId))
// Process with scoped dependencies
val processor: RequestHandler = requestScope.get()
return processor.handle()
} finally {
// Always cleanup
requestScope.close()
}
}
}class ApplicationArchitecture {
fun setupScopes(): Triple<Scope, Scope, Scope> {
val koin = koinApplication { modules(appModules) }.koin
// Application level - global for app lifetime
val appScope = koin.createScope<Application>("app")
// Feature level - for specific feature lifetime
val featureScope = koin.createScope<FeatureContext>("feature-user")
featureScope.linkTo(appScope)
// Request level - for individual request lifetime
val requestScope = koin.createScope<RequestContext>("request-123")
requestScope.linkTo(featureScope, appScope)
return Triple(appScope, featureScope, requestScope)
}
}// Different scope contexts for different features
val eCommerceModule = module {
scope<ShoppingCart> {
scoped<CartService> { CartService() }
scoped<PriceCalculator> { PriceCalculator() }
}
scope<Checkout> {
scoped<PaymentProcessor> { PaymentProcessor() }
scoped<OrderService> { OrderService() }
factory<Receipt> { Receipt() }
}
}
// Usage
val cartScope = koin.createScope<ShoppingCart>("cart-user123")
val checkoutScope = koin.createScope<Checkout>("checkout-order456")
// Services are isolated per scope
val cartService1 = cartScope.get<CartService>() // ShoppingCart scope instance
val paymentProcessor = checkoutScope.get<PaymentProcessor>() // Checkout scope instanceScope-related exceptions:
// ClosedScopeException - using closed scope
val scope = koin.createScope<UserSession>("test")
scope.close()
// val service = scope.get<Service>() // Throws ClosedScopeException
// ScopeNotCreatedException - accessing non-existent scope
// val nonExistent = koin.getScope("does-not-exist") // Throws ScopeNotCreatedException
// ScopeAlreadyCreatedException - creating duplicate scope
val scope1 = koin.createScope<UserSession>("duplicate")
// val scope2 = koin.createScope<UserSession>("duplicate") // May throw ScopeAlreadyCreatedException// Good - clear lifecycle boundaries
class UserSessionScope {
companion object {
fun create(koin: Koin, userId: String): Scope {
return koin.createScope<UserSession>("user-$userId")
}
}
}// Use try-finally or use pattern
fun processUserRequest(userId: String) {
val userScope = koin.createScope<UserSession>("user-$userId")
try {
val service: UserService = userScope.get()
service.processRequest()
} finally {
userScope.close() // Always cleanup
}
}// Child scopes should link to parents for dependency resolution
val appScope = koin.createScope<Application>("app")
val userScope = koin.createScope<UserSession>("user-123")
userScope.linkTo(appScope) // User scope can access app-level dependenciesclass ResourceCleanupCallback : ScopeCallback {
override fun onScopeClose(scope: Scope) {
// Cleanup any resources that need explicit cleanup
scope.getOrNull<FileResource>()?.close()
scope.getOrNull<NetworkConnection>()?.disconnect()
}
}Scopes provide powerful lifecycle management capabilities that enable clean resource management and component isolation in complex applications.
Install with Tessl CLI
npx tessl i tessl/maven-io-insert-koin--koin-core-iossimulatorarm64