Kotlin DSL Marker
The @DslMarker annotation limits which receivers a nested builder block can reach. A block in a marked builder resolves its calls against its own receiver only.
Declaring the marker and marking one builder
BasketDsl is an annotation class marked with @DslMarker. The Basket class carries @BasketDsl, and its builder works as before.
@DslMarker
annotation class BasketDsl
@BasketDsl
class Basket {
var label = ""
fun name(value: String) {
label = value
}
}
fun basketOf(init: Basket.() -> Unit): Basket {
val basket = Basket()
basket.init()
return basket
}
fun main() {
val basket = basketOf {
name("fruit")
}
println(basket.label)
}The marker on both levels of a nested build
Shelf and Basket both carry the marker. Inside a basket block, name resolves against the Basket only, and each stored label prints.
@DslMarker
annotation class BasketDsl
@BasketDsl
class Basket {
var label = ""
fun name(value: String) {
label = value
}
}
@BasketDsl
class Shelf {
val baskets = mutableListOf<Basket>()
fun basket(init: Basket.() -> Unit) {
val basket = Basket()
basket.init()
baskets.add(basket)
}
}
fun shelfOf(init: Shelf.() -> Unit): Shelf {
val shelf = Shelf()
shelf.init()
return shelf
}
fun main() {
val shelf = shelfOf {
basket {
name("fruit")
}
basket {
name("bread")
}
}
shelf.baskets.forEach { println(it.label) }
}Example
@DslMarker
annotation class BasketDsl
@BasketDsl
class Basket {
var label = ""
val items = mutableListOf<String>()
fun name(value: String) {
label = value
}
fun item(value: String) {
items.add(value)
}
}
@BasketDsl
class Crate {
val baskets = mutableListOf<Basket>()
fun basket(init: Basket.() -> Unit) {
val basket = Basket()
basket.init()
baskets.add(basket)
}
}
fun crateOf(init: Crate.() -> Unit): Crate {
val crate = Crate()
crate.init()
return crate
}
fun main() {
val crate = crateOf {
basket {
name("fruit")
item("apple")
item("pear")
}
}
val basket = crate.baskets[0]
println(basket.label)
println(basket.items.joinToString(", "))
}Key points
- An annotation class marked with @DslMarker becomes a marker for builder receivers.
- The marker annotation is placed on each builder class that takes part in the builder.
- Inside a marked inner block, member calls resolve against the inner receiver.
- The marker stops a nested block from quietly calling the outer builder methods.
- The objects the builder creates stay the same. Only name resolution inside the blocks changes.
In this example, Crate and Basket both carry the BasketDsl marker. The basket block sets the label and adds two items through the Basket receiver only. The stored label and the joined items print at the end.
Note
Both the outer and the inner builder class carry the marker in the examples here.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Kotlin Playground
Exercise
What does @DslMarker change inside a nested builder block?