Kotlin Type-Safe Builders
A type-safe builder nests receiver lambda blocks so the code mirrors the structure of the object tree it builds. Each nested block fills one level of that tree.
One level of the tree
A Section holds entries. The sectionOf builder runs a block on a fresh Section and returns it filled.
class Section(val title: String) {
val entries = mutableListOf<String>()
fun entry(name: String) {
entries.add(name)
}
}
fun sectionOf(title: String, init: Section.() -> Unit): Section {
val section = Section(title)
section.init()
return section
}
fun main() {
val section = sectionOf("Drinks") {
entry("Tea")
entry("Coffee")
}
println(section.title)
section.entries.forEach { println(it) }
}A Menu that builds its sections
The section member of Menu creates a Section, runs the nested block on it, and adds it to the list. The block inside section runs on the Section, not on the Menu.
class Section(val title: String) {
val entries = mutableListOf<String>()
fun entry(name: String) {
entries.add(name)
}
}
class Menu {
val sections = mutableListOf<Section>()
fun section(title: String, init: Section.() -> Unit) {
val section = Section(title)
section.init()
sections.add(section)
}
}
fun menuOf(init: Menu.() -> Unit): Menu {
val menu = Menu()
menu.init()
return menu
}
fun main() {
val menu = menuOf {
section("Drinks") {
entry("Tea")
}
section("Mains") {
entry("Soup")
}
}
println(menu.sections.size)
println(menu.sections[0].title)
println(menu.sections[1].title)
}Example
class Section(val title: String) {
val entries = mutableListOf<String>()
fun entry(name: String) {
entries.add(name)
}
}
class Menu {
val sections = mutableListOf<Section>()
fun section(title: String, init: Section.() -> Unit) {
val section = Section(title)
section.init()
sections.add(section)
}
}
fun menuOf(init: Menu.() -> Unit): Menu {
val menu = Menu()
menu.init()
return menu
}
fun main() {
val menu = menuOf {
section("Drinks") {
entry("Tea")
entry("Coffee")
}
section("Mains") {
entry("Soup")
entry("Salad")
}
}
menu.sections.forEach { section ->
println(section.title)
section.entries.forEach { println("- $it") }
}
}Key points
- An outer builder class holds a list of the inner objects it collects.
- A member function on the outer builder creates an inner object, runs a block on it, and stores it.
- A top-level builder function creates the outer object, runs the caller block on it, and returns it.
- The nested blocks follow the shape of the data, so the build reads like the tree it makes.
- Walking the built tree in insertion order shows each section and its entries.
In this example, menuOf runs its block on a fresh Menu. Each section call builds one Section through its own nested block and stores it. The final loops walk the tree in insertion order and print each section title with its entries.
Note
Type-safe builders rest on the receiver lambdas from the previous lesson.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Kotlin Playground
Exercise
In the menu builder, what does the block passed to section run on?