Bindings

A binding decides how and when a member produces its value. All bindings are built with the factories of the Binding companion object.

Factory Body Behaviour

value(v)

none

Always returns the same pre-built v.

provider { }

M.() → T

Runs the body on every access.

factory { }

M.(A) → T

Runs the body on every access, with the caller’s argument.

single { }

M.() → T

Runs the body once, on first access, and keeps the result forever.

weakSingle { }

M.() → T

Like single, but keeps the result behind a weak reference.

multiplex { }

(A) → Binding<M, Unit, T>

Builds and keeps one sub-binding per distinct argument value.

detachedSingle { }

() → T

Like single, but never sees the module, so every copy of the module shares it.

detachedWeakSingle { }

() → T

Like weakSingle, but never sees the module, so every copy of the module shares it.

detachedMultiplex { }

(A) → Binding<Any?, Unit, T>

Like multiplex, but never sees the module, so every copy of the module shares it.

value

value binds a value that already exists. Nothing is ever computed:

Binding.value(Config(debug = true))

Use it for configuration, constants, and objects that were built outside of the module.

provider

provider runs its body every time the member is accessed, producing a fresh value each time:

Binding.provider { Clock.System.now() }

Use it for values that must never be shared, or that are cheap and change over time.

single

single runs its body the first time the member is accessed, then returns that same instance forever:

Binding.single { SqlDatabase(databaseUrl) }

Use it for services that should exist once per module: clients, databases, repositories.

The body runs lazily: a single that is never accessed is never built.

A single guarantees one instance per module, not one instance in the application. Every copy of a module gets its own copy of the single, which builds its own instance:

val copy = module.copy(name = Binding.value("Jane"))

copy.database === module.database // false (1)
1 Both modules built their own SqlDatabase.

If the application holds both modules, it holds two instances. For a resource that must exist only once, like a connection pool, use detachedSingle, build the module once and pass it around, or use shadow instead.

shadow does not copy the bindings you leave out: it delegates them to the parent, which keeps the only instance. copy re-runs each of them against the new module, so the instance can even differ in content when it reads a member you overrode. Prefer shadow to copy unless you do want a separate instance, for example to give a test its own state.

single takes an optional thread-safety mode, described in Thread safety:

Binding.single(SingleThreadSafety.Published) { ExpensiveButPureValue() }

weakSingle

weakSingle behaves like single, except that the module only holds a weak reference to the instance. Once nothing else references it, the instance may be garbage collected, and the next access builds a new one:

Binding.weakSingle { ImageCache() }

Use it for values that are expensive but can be rebuilt, and that you do not want to keep in memory just because a module can produce them. The produced type must be non-nullable.

Like single, it takes an optional thread-safety mode.

factory

factory is for members that take an argument. It runs its body on every access, passing the caller’s argument:

@DIModule
interface AppModule {
    fun user(id: Int): User
}

AppModule(
    user = Binding.factory { id -> User(id) },
)

factory is the argument-taking equivalent of provider: nothing is kept between calls. Member arguments explains how members with several parameters or vararg parameters are bound.

multiplex

multiplex gives an argument-taking member a lifecycle. For each distinct argument value, it calls its lambda once to build a sub-binding, keeps it, and delegates every call with that argument to it:

@DIModule
interface AppModule {
    fun connection(host: String): Connection
}

AppModule(
    connection = Binding.multiplex { host -> Binding.single { Connection(host) } }, (1)
)
1 One Connection per host, each built once and kept forever.

The sub-binding can be any no-argument binding: single, weakSingle, provider, or even value.

The per-argument cache is never evicted. multiplex is for a small, bounded set of arguments: hosts, tenants, enum values. Keyed on an unbounded value, like a user id, a request id or a timestamp, it is a memory leak: use factory there.

An array argument, or a generated arguments class holding an array, never equals a fresh one. With such an argument, every call misses the cache, which grows by one entry per call.

Detached bindings

detachedSingle, detachedWeakSingle and detachedMultiplex behave like single, weakSingle and multiplex, except that they are detached from the module. Their body takes no receiver, so it cannot read the module, and they return a Binding<Any?, …​>, which fits a member of any module:

AppModule(
    pool = Binding.detachedSingle { ConnectionPool(url = "jdbc:...") }, (1)
    connection = Binding.detachedMultiplex { host -> Binding.detachedSingle { Connection(host) } }, (2)
)
1 One ConnectionPool in the application, however many times the module is copied.
2 The sub-bindings are given null as their module, so they cannot reach it either.

Since nothing a detached binding builds can depend on the module, copy keeps the binding itself instead of copying it: the module, its copies, its shadows and the modules composed from it all share one instance, and it is built only once.

A detached body cannot call the other members of the module. Capture the values it needs when you build it, or use a plain single when the instance has to read the module.

They take the same optional thread-safety mode as single and weakSingle. SuspendBinding has the same three factories, with suspend bodies.

Choosing a binding

You want…​ Use

a value you already have

value

one instance for the lifetime of the module

single

one instance, as long as someone uses it

weakSingle

one instance for the whole application, shared by every copy of the module

detachedSingle

a new instance every time

provider

a new instance every time, built from an argument

factory

one instance per argument value, from a small set

multiplex