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 |
|---|---|---|
|
none |
Always returns the same pre-built |
|
|
Runs the body on every access. |
|
|
Runs the body on every access, with the caller’s argument. |
|
|
Runs the body once, on first access, and keeps the result forever. |
|
|
Like |
|
|
Builds and keeps one sub-binding per distinct argument value. |
|
|
Like |
|
|
Like |
|
|
Like |
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
If the application holds both modules, it holds two instances.
For a resource that must exist only once, like a connection pool, use
|
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.
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 |
|
one instance for the lifetime of the module |
|
one instance, as long as someone uses it |
|
one instance for the whole application, shared by every copy of the module |
|
a new instance every time |
|
a new instance every time, built from an argument |
|
one instance per argument value, from a small set |
|