📖 Scenario: Imagine you are building a simple Kotlin DSL (Domain Specific Language) to create a menu for a restaurant. You want to organize the menu into sections like Starters and Main Courses. To avoid confusion when adding items to different sections, you will use Kotlin's @DslMarker annotation to control the scope and prevent mistakes.
🎯 Goal: You will build a Kotlin DSL that lets you define a menu with sections and items. You will use @DslMarker to keep the scopes clear so that you cannot accidentally add a menu item to the wrong section.
📋 What You'll Learn
Create a @DslMarker annotation called MenuDsl
Define classes Menu, Section, and Item with DSL builder functions
Use @MenuDsl on the DSL classes to control scope
Build a menu with two sections: Starters and Main Courses
Add items to each section using the DSL
Print the menu showing sections and their items
💡 Why This Matters
🌍 Real World
DSLs help create readable and easy-to-write code for specific tasks like building menus, UI layouts, or configuration files.
💼 Career
Understanding Kotlin DSLs and scope control with <code>@DslMarker</code> is useful for Android developers and Kotlin backend developers who build custom DSLs or use libraries with DSLs.
Progress0 / 4 steps
1
Create the DSL marker and data classes
Create a @DslMarker annotation called MenuDsl. Then create three classes: Menu, Section, and Item. Each class should have a name property of type String. The Menu class should have a mutable list of Section called sections. The Section class should have a mutable list of Item called items. Annotate all three classes with @MenuDsl.
Kotlin
Hint
Start by defining the @DslMarker annotation. Then create the classes with the required properties and annotate them with @MenuDsl.
2
Add builder functions for Menu and Section
Add a function section inside the Menu class that takes a name: String and a lambda with receiver Section. Inside this function, create a new Section with the given name, apply the lambda to it, and add it to the sections list. Also add a function item inside the Section class that takes a name: String and creates an Item with that name, adding it to the items list.
Kotlin
Hint
Inside Menu, write a section function that creates and adds a Section. Inside Section, write an item function that adds an Item.
3
Create the menu using the DSL
Create a variable menu by calling Menu("Dinner Menu") and use the section function to add two sections: "Starters" and "Main Courses". Inside the "Starters" section, add items "Soup" and "Salad". Inside the "Main Courses" section, add items "Steak" and "Pasta".
Kotlin
Hint
Create the menu variable using Menu("Dinner Menu") and add two sections with their items using the DSL functions.
4
Print the menu with sections and items
Write code to print the menu name, then for each section print its name, and for each item in the section print its name indented. Use a for loop with variables section and item to iterate over menu.sections and section.items. The output should look like this exactly:
Dinner Menu
Starters
Soup
Salad
Main Courses
Steak
Pasta
Kotlin
Hint
Print the menu name first. Then use nested for loops to print each section and its items with indentation.
Practice
(1/5)
1. What is the main purpose of the @DslMarker annotation in Kotlin DSLs?
easy
A. To limit function visibility and prevent accidental calls to outer DSL scopes inside nested blocks.
B. To mark a function as deprecated in DSL usage.
C. To automatically generate DSL builder classes.
D. To enable inline functions in DSL builders.
Solution
Step 1: Understand DSL nesting problem
In nested DSL blocks, functions from outer scopes can be accidentally called inside inner blocks, causing confusion.
Step 2: Role of @DslMarker
@DslMarker annotation marks DSL builder classes to restrict visibility of outer scope functions inside nested DSL blocks, preventing accidental calls.
Final Answer:
To limit function visibility and prevent accidental calls to outer DSL scopes inside nested blocks. -> Option A
Assuming all functions from outer scopes are visible
Ignoring effect of multiple DSL markers
Confusing which DSL marker applies where
4. Given the following Kotlin DSL code, what is the error and how to fix it?
@DslMarker
annotation class MyDsl
@MyDsl
class Outer {
fun outerFun() = println("Outer")
fun inner(block: Inner.() -> Unit) {
Inner().apply(block)
}
}
@MyDsl
class Inner {
fun innerFun() = println("Inner")
}
fun main() {
Outer().apply {
outerFun()
inner {
outerFun() // Error here
innerFun()
}
}
}
medium
A. Error: innerFun() is not visible; fix by removing @MyDsl annotation.
B. Error: Cannot call outerFun() inside inner block due to DSL marker scope control; fix by qualifying with this@Outer.outerFun().
C. Error: inner block is not called; fix by changing Inner().block() to Inner().apply(block).
D. No error; code runs fine.
Solution
Step 1: Identify cause of error
Inside the inner block, outerFun() is not directly visible because @MyDsl restricts access to outer scope functions.
Step 2: Fix by explicit qualification
Use explicit label this@Outer.outerFun() to call the outer function inside the inner block, bypassing DSL marker restriction.
Final Answer:
Error: Cannot call outerFun() inside inner block due to DSL marker scope control; fix by qualifying with this@Outer.outerFun(). -> Option B
Quick Check:
Use explicit this@Outer to access outer functions [OK]
Hint: Use this@Outer to call outer functions blocked by DSL marker [OK]
Common Mistakes:
Removing @DslMarker annotation instead of qualifying call
Ignoring the need for explicit receiver labels
Assuming innerFun() visibility causes error
5. You want to create a Kotlin DSL for building HTML-like structures with nested tags. You define two builder classes HtmlBuilder and BodyBuilder, both annotated with the same @DslMarker annotation. How does using @DslMarker help prevent mistakes when writing nested DSL code like html { body { div { ... } } }?
hard
A. It prevents calling functions from HtmlBuilder inside BodyBuilder blocks unless explicitly qualified, avoiding accidental outer scope calls.
B. It automatically generates HTML tags for each builder class.
C. It allows calling any function from HtmlBuilder inside BodyBuilder without restrictions.
D. It disables nested DSL blocks to simplify code.
Solution
Step 1: Understand nested DSL structure
In nested DSLs like HTML, inner blocks (BodyBuilder) are inside outer blocks (HtmlBuilder).
Step 2: Role of @DslMarker in nested builders
@DslMarker annotation restricts visibility of outer builder functions inside inner builder blocks, preventing accidental calls to outer scope functions without explicit qualification.
Final Answer:
It prevents calling functions from HtmlBuilder inside BodyBuilder blocks unless explicitly qualified, avoiding accidental outer scope calls. -> Option A