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=20260117T134436Z&X-Goog-Expires=3600&X-Goog-SignedHeaders=host&X-Goog-Signature=3c2ce72d5bd09d0b3eb5a981ac7159b8f48a0486592b063b05fb09f9e9d48a0354115d369a684367cb384e02a1529f9eb2f244260512b35c0ccee41ee8f4f219ad0b9e130cada1d907ceb9b8eedfc09f88bb88d4a93dc593c10c16a0b127360ede38c74b044b7ef21a4035dbe21832841656be10ef58ab611846bd13fdbaaf73d668ae88f1d4daec7004d02d608660b0129f076b78380aa23166a28feb49188b292f61c096196eac34d58eebc7397363662f029a97737c8d7ebde01f726b7dcae107fdacd38d75937be5e1d62210cb579681e116bc9b2bc12df38773429f8aeeb32ed7e90a574f261536ed0dc7464c6c5f9ecbd96ecc2f06e4e754af14cc2861
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