Creating a Kotlin DSL for Jetpack Compose Components

Introduction to Kotlin DSL

Kotlin DSL (Domain-Specific Language) is a powerful feature of Kotlin that allows you to create declarative and intuitive APIs. It is widely used in the Kotlin ecosystem, with the Gradle Kotlin DSL being a classic …


This content originally appeared on DEV Community and was authored by Rafael Henrique

Introduction to Kotlin DSL

Kotlin DSL (Domain-Specific Language) is a powerful feature of Kotlin that allows you to create declarative and intuitive APIs. It is widely used in the Kotlin ecosystem, with the Gradle Kotlin DSL being a classic example. With Kotlin DSL, you can transform complex configurations into simple and organized blocks.

But what if we could bring the same concept to Jetpack Compose? Imagine defining your UI components using a declarative and flexible approach. This article explores how to build a custom DSL for a Compose component, like CustomButton.
CustomButton

This article was originally published on Medium: Creating a Kotlin DSL for Jetpack Compose Components

Why Create a DSL for Compose Components?

The declarative approach of Jetpack Compose already makes UI definitions more intuitive. However, when dealing with reusable and highly configurable components, a DSL provides additional advantages:

  • Clear Readability: Configurations are organized into logical blocks, making them easier to understand.
  • Reusability: You can encapsulate repetitive logic and create simple APIs for reuse.
  • Extensibility: Adding new configurations is straightforward and intuitive.

Let’s build a practical example to better understand how this works.

Building a DSL from Scratch

Defining the Configuration Class

First, we create a class to hold the configurations for our component:

class CustomButtonConfig {
    var text: String = ""
    var icon: ImageVector? = null
    var onClick: (() -> Unit)? = null
    var backgroundColor: Color = MaterialTheme.colors.primary
    var textColor: Color = Color.White
    val shapes = mutableListOf<ComposedShape>()
}

This class defines the properties that users can configure in the component, including composed shapes.

Creating the @DslMarker Annotation

To isolate the scope of the DSL and avoid conflicts between different blocks, we create a custom annotation:

@DslMarker
annotation class CustomButtonDSL

We update the relevant classes to apply this annotation:

@CustomButtonDSL
class ComposedShape {
    val shapes = mutableListOf<ShapeConfig>()
}

@CustomButtonDSL
sealed class ShapeConfig {
    data class TriangleConfig(var base: Float, var height: Float, var color: Color) : ShapeConfig()
    data class RhombusConfig(var sideLength: Float, var angle: Float, var color: Color) : ShapeConfig()
}

Creating DSL Functions for Shapes in the Button Context

We add functions to configure shapes within the button DSL:

fun CustomButtonConfig.composedShape(block: ComposedShape.() -> Unit) {
    shapes.add(ComposedShape().apply(block))
}

fun ComposedShape.triangle(base: Float, height: Float, color: Color) {
    shapes.add(ShapeConfig.TriangleConfig(base, height, color))
}

fun ComposedShape.rhombus(sideLength: Float, angle: Float, color: Color) {
    shapes.add(ShapeConfig.RhombusConfig(sideLength, angle, color))
}

Updating the CustomButton Component

We modify the customButton function to render composed shapes:

@Composable
fun customButton(configure: CustomButtonConfig.() -> Unit) {
    val config = CustomButtonConfig().apply(configure)

    Button(
        onClick = config.onClick ?: {},
        colors = ButtonDefaults.buttonColors(backgroundColor = config.backgroundColor),
        modifier = Modifier.padding(8.dp)
    ) {
        Column(
            modifier = Modifier.fillMaxWidth(),
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            // Render Shapes
            config.shapes.forEach { composedShape ->
                composedShape.shapes.forEach { shape ->
                    when (shape) {
                        is ShapeConfig.TriangleConfig -> {
                            Text(
                                text = "\u25B2", // Triangle representation
                                color = shape.color,
                                modifier = Modifier.size(shape.base.dp, shape.height.dp)
                            )
                        }

                        is ShapeConfig.RhombusConfig -> {
                            Text(
                                text = "\u25C6", // Rhombus representation
                                color = shape.color,
                                modifier = Modifier.size(shape.sideLength.dp)
                            )
                        }
                    }
                }
            }
            // Render Text and Icon
            Row(
                horizontalArrangement = Arrangement.Center,
                verticalAlignment = Alignment.CenterVertically
            ) {
                config.icon?.let {
                    Icon(imageVector = it, contentDescription = null, tint = config.textColor)
                }
                if (config.icon != null) Spacer(modifier = Modifier.width(8.dp))
                Text(text = config.text, color = config.textColor)
            }
        }
    }
}

Practical Examples

Example 1: Button with Triangle and Rhombus

customButton {
    text = "With Shapes"
    textColor = Color.Black
    backgroundColor = Color.LightGray
    onClick = { println("Button with shapes clicked!") }

    composedShape {
        triangle(base = 50f, height = 30f, color = Color.Red)
        rhombus(sideLength = 40f, angle = 45f, color = Color.Blue)
    }
}

Example 2: Simple Button

customButton {
    text = "Click Here"
    textColor = Color.Black
    onClick = { println("Button clicked!") }
}

Example 3: Button with Icon

customButton {
    text = "Favorite"
    icon = Icons.Default.Favorite
    textColor = Color.Black
    onClick = { println("Favorite clicked!") }
}

Example 4: Button with Custom Style

customButton {
    text = "Send"
    icon = Icons.Default.Send
    onClick = { println("Send clicked!") }
    backgroundColor = Color.Green
    textColor = Color.White
}

Benefits of Kotlin DSL

  1. Improved Readability: Configurations are organized into clear, logical blocks.
  2. Reduced Boilerplate: Less repetitive code, more focus on what matters.
  3. Extensibility: New features can be added to the DSL without altering existing code.
  4. Productivity: The declarative API speeds up development and enhances the developer experience.

Examples

component image example

Conclusion

Creating a Kotlin DSL for Compose components allows you to leverage the power of Kotlin to simplify and organize your UI in a declarative way. Besides making the code more readable, this approach promotes reusability and scalability of components.

Why not give it a try and create your own DSL? With Kotlin, the possibilities are endless. Let’s get to work!


This content originally appeared on DEV Community and was authored by Rafael Henrique


Print Share Comment Cite Upload Translate Updates
APA

Rafael Henrique | Sciencx (2025-01-30T20:14:19+00:00) Creating a Kotlin DSL for Jetpack Compose Components. Retrieved from https://www.scien.cx/2025/01/30/creating-a-kotlin-dsl-for-jetpack-compose-components/

MLA
" » Creating a Kotlin DSL for Jetpack Compose Components." Rafael Henrique | Sciencx - Thursday January 30, 2025, https://www.scien.cx/2025/01/30/creating-a-kotlin-dsl-for-jetpack-compose-components/
HARVARD
Rafael Henrique | Sciencx Thursday January 30, 2025 » Creating a Kotlin DSL for Jetpack Compose Components., viewed ,<https://www.scien.cx/2025/01/30/creating-a-kotlin-dsl-for-jetpack-compose-components/>
VANCOUVER
Rafael Henrique | Sciencx - » Creating a Kotlin DSL for Jetpack Compose Components. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2025/01/30/creating-a-kotlin-dsl-for-jetpack-compose-components/
CHICAGO
" » Creating a Kotlin DSL for Jetpack Compose Components." Rafael Henrique | Sciencx - Accessed . https://www.scien.cx/2025/01/30/creating-a-kotlin-dsl-for-jetpack-compose-components/
IEEE
" » Creating a Kotlin DSL for Jetpack Compose Components." Rafael Henrique | Sciencx [Online]. Available: https://www.scien.cx/2025/01/30/creating-a-kotlin-dsl-for-jetpack-compose-components/. [Accessed: ]
rf:citation
» Creating a Kotlin DSL for Jetpack Compose Components | Rafael Henrique | Sciencx | https://www.scien.cx/2025/01/30/creating-a-kotlin-dsl-for-jetpack-compose-components/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.