3 of 5

Scopes

One instance per scope — shared within a context, isolated between contexts.

Basic scoped bindings

Scoped bindings create one instance per scope — a singleton within a lifecycle boundary. Useful when a dependency must be shared within a context (like an HTTP request) but isolated between contexts.

kotlin
container.scoped<DbConnection> { DbConnection(resolve<Config>()) }

// Scoped bindings can only be resolved within a scope:
container.resolve<DbConnection>() // throws ScopeRequiredException

val scope = container.child()
scope.resolve<DbConnection>()     // creates instance
scope.resolve<DbConnection>()     // same instance
scope.close()                      // instance disposed

Block-based scopes

Use the block syntax for automatic cleanup — the scope closes when the block ends:

kotlin
container.scope { scope ->
    val db = scope.resolve<DbConnection>()
    // use db...
}  // scope auto-closes here

Dispose hooks

Attach onClose to run cleanup when the scope closes:

kotlin
container.scoped<DbConnection> { DbConnection() }
    .onClose { it.disconnect() }

Instances implementing AutoCloseable are closed automatically — no hook needed:

kotlin
container.scoped<InputStream> { FileInputStream("data.bin") }
// .close() called automatically when scope closes

If both onClose and AutoCloseable apply, only the explicit onClose runs.

Nested scopes

Scopes can be nested. Each scope gets its own scoped instances, and closing a parent cascades to children (deepest first):

kotlin
container.scope { outer ->
    val outerDb = outer.resolve<DbConnection>()

    outer.scope { inner ->
        val innerDb = inner.resolve<DbConnection>()  // different instance
        // inner closes first
    }
    // outer closes after
}

Scopes as child containers

A scope is a full Container — you can register ad-hoc bindings on it:

kotlin
container.scope { scope ->
    scope.singleton<RequestId> { RequestId.generate() }
    scope.resolve<RequestHandler>()  // can depend on RequestId
}

Contextual scopes

Use service providers to set up different scope contexts. The scope's purpose is defined by what you register on it:

kotlin
// HTTP request scope
fun handleRequest(container: Container, request: HttpRequest) {
    container.scope { scope ->
        scope.register(RequestScopeProvider(request))
        scope.resolve<RequestHandler>().handle()
    }
}

// Background job scope
fun processJob(container: Container, job: Job) {
    container.scope { scope ->
        scope.register(JobScopeProvider(job))
        scope.resolve<JobProcessor>().run()
    }
}

Android

In Android, the system controls Activity and Fragment lifecycles — you can't wrap them in a scope { } block. Instead, tie scopes to lifecycle callbacks:

kotlin
class MyActivity : AppCompatActivity() {
    private lateinit var scope: Scope

    override fun onCreate(savedInstanceState: Bundle?) {
        scope = appContainer.child()
        scope.register(ActivityScopeProvider(this))
        val presenter = scope.resolve<Presenter>()
    }

    override fun onDestroy() {
        scope.close()
        super.onDestroy()
    }
}

Next steps

Learn how to organize registrations into reusable modules with service providers.