Default implementations
A member that has a body in the interface does not need a binding.
Its builder parameter is a nullable binding that defaults to null, meaning "keep the interface’s own implementation".
GreetingModule.kt
@DIModule
interface GreetingModule {
val name: String
val greeting: String get() = "Hello, $name"
fun shout(): String = "$greeting!!!"
}
Only the abstract member must be bound:
val module = GreetingModule(name = Binding.value("World"))
module.greeting // "Hello, World"
module.shout() // "Hello, World!!!"
Overriding a default
Any default can still be bound, which replaces the interface’s body:
val module = GreetingModule(
name = Binding.value("World"),
greeting = Binding.provider { "Hi $name" },
)
module.shout() // "Hi World!!!" (1)
| 1 | The default body of shout() reads the overridden greeting. |
Default bodies run against the module that was actually built. A default that reads another member therefore observes whatever that member was bound to.
Defaults and lifecycles
A default body behaves like a provider: it runs on every access, since it is ordinary interface code.
To memoize a member that has a sensible default, bind it explicitly:
GreetingModule(
name = Binding.value("World"),
greeting = Binding.single { "Hello, $name" },
)
| Default implementations are a good place for derived values, and for convenience functions that combine other members of the module. |