Arun Pandian M

Arun Pandian M

Android Dev | Full-Stack & AI Learner

🍕 Building a Pizza Like Jetpack Compose: A Modifier Story

https://storage.googleapis.com/lambdabricks-cd393.firebasestorage.app/compose_pizza.webp?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=firebase-adminsdk-fbsvc%40lambdabricks-cd393.iam.gserviceaccount.com%2F20260117%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20260117T151823Z&X-Goog-Expires=3600&X-Goog-SignedHeaders=host&X-Goog-Signature=289d5119ba4f4869470165b231f20e021d448b5dbf84f3678a18940c7c04515e12de2b1df81abf6a44de001268a48edf93cdd2b9ca4392a3e96770799b80b1dc510a7da97685f3275fd50bca71c3761444c8b93d8573ceb6e9c7ee6e2ec34c494979cb4eefc95520d6a21ad05f14dd03e94717b491850a4719c817ed2abb5a95417c0d6830a2136f24ed49daef2d944e45f3c450cf1cd8091ee99934955ce579e3d811faf9f5b66a4d8fddf1fd7915a81aaf4f693009b067ea8bea1e5c55e8b90f623f250052e0ba8750103236612decdea2f751b91c148248f33a885799f8d0a46aa69dd9dcba40512cd578c46ce062544745214438481e19c3cbf69ca82d00
Just like the LEGO pieces add toppings and a crust to this pizza, modifiers allow you to customize a Composable with styling, layout, and behavior

I love pizza. And I love Jetpack Compose. Turns out, they have a lot in common. Stick with me, and I’ll show you why building a pizza can teach you how Compose modifiers actually work.

Modifiers: The Secret Sauce

In Compose, every UI element — a Text, a Box, a Column — can be decorated with modifiers. Think of a modifier as a little instruction: “Add padding here,” or “Make this background red.” You can chain these modifiers:

Modifier
    .padding(8.dp)
    .background(Color.Red)

Each modifier is applied in sequence, just like adding ingredients to a pizza.

So I decided: why not make a Pizza DSL using the same concept?

Step 1: Building the Chain

I created a Chain interface — our version of Compose’s Modifier. Each step, like adding dough, sauce, or cheese, is a ChainUnit. And just like Compose, we can chain them with then():

Chain.pizzaDough("Thin Crust")
    .pizzaSauce("Tomato Basil")
    .pizzaTopping("Mushroom")
    .pizzaCheese("Mozzarella")

Here’s the magic:

  • ChainUnit = a single modification (like Modifier.padding)
  • then() = “do this, then that”
  • fold() = actually apply all the steps in order
  • Without fold, we just have a list of instructions. Fold executes them, building the final pizza.

    Step 2: Fold — The Assembly Line

    Think of fold like an assembly line in a pizza shop:

    1. Dough chef lays down the crust

    2.Sauce chef spreads the sauce

    3.Topping chef sprinkles mushrooms

    4.Cheese chef adds mozzarella

    Each chef doesn’t need to know about the others — they just follow the sequence. This is exactly what fold() does

    chain.fold(Pizza()) { pizza, unit ->
        when(unit) {
            is Dough -> pizza.dough = unit
            is Sauce -> pizza.sauce = unit
            is Topping -> pizza.toppings.add(unit)
            is Cheese -> pizza.cheese = unit
        }
        pizza
    }

    Order matters. Fold ensures the pizza is assembled step by step, just like Compose applies modifiers in order.

    Step 3: Scope-Specific Modifiers

    Some pizzas are special. Pizza Hut likes stuffed crust, and Dominos loves double cheese. We can model this using scopes, similar to BoxScope or ColumnScope in Compose:

    PizzaHutScope().run {
        Chain
            .pizzaDough("Thin Crust")
            .pizzaSauce("Marinara")
            .pizzaTopping("Pepperoni")
            .pizzaCheese("Mozzarella")
            .stuffedCrust()  // only valid in PizzaHutScope
            .let { Pizza.compose(it) }
    }
    

    Step 4: Why Functional Chains Beat Builders

    You might wonder, “Why not just use a builder?” Builders mutate objects step by step, which is fine for small things. But functional chains shine when:

  • You want reusable sequences of modifiers
  • You want predictable order without side effects
  • You want a DSL that reads naturally
  • Compare:

    // Builder pattern
    PizzaBuilder().setDough(...).setSauce(...).addTopping(...).build()
    
    // Functional chain
    Chain.pizzaDough(...).pizzaSauce(...).pizzaTopping(...).fold(Pizza()) { ... }
    

    The chain reads like a recipe. It’s declarative, composable, and fits perfectly with scopes.

    Step 5: Putting It All Together

    Here’s a Pizza Hut example:

    val pizzaHut = PizzaHutScope().run {
        Chain
            .pizzaDough("Thin Crust")
            .pizzaSauce("Marinara")
            .pizzaTopping("Pepperoni")
            .pizzaCheese("Mozzarella")
            .stuffedCrust()
            .let { Pizza.compose(it) }
    }
    
    println(pizzaHut.describe())

    See how naturally it reads? You assemble a pizza step by step, respecting scope rules, just like Compose applies modifiers to UI elements.

    🔑 Takeaways

    Modifiers are just small, reusable steps
    Chains and fold ensure order and composability
    Scope-level extensions enforce context rules
    Functional chain > builder for readability and reuse
    DSL = human-readable recipe, like Compose modifiers reading like a story

    In short, building a pizza like this feels like Compose in action. You can chain, scope, and fold ingredients — or modifiers — creating a final product that’s perfectly layered, predictable, and reusable.

    You’ve seen how we can build a pizza step by step using fold-based chains, scope-specific modifiers, and a Compose-inspired DSL. If you want to play with the code yourself, experiment, or tweak it, I’ve put the entire Kotlin sample in a GitHub Gist.

    Go ahead, copy it, run it, and make your own pizza combinations: https://gist.github.com/arunpandian22/3ae30ee154a223a4137cf64f13732659

    Have fun exploring, and may your pizza — and your code — always come out perfectly! 🍕👩‍💻

    #android_development#jetpack_compose#software_design_patterns#android_kotlin#functional_programming_kotlin#kotlin_dsl#compose_patterns#compose_modifier_chain#developer_storytelling#declarative_ui#compose_ui#compose_modifiers#kotlin_functional_style#compose_best_practices#kotlin_tutorial