Phase 1 · Getting Started with ComposeProject 3

Cafe Menu Board

Display three sample cafe items using one reusable card function. Add a basic Show/Hide button to reveal ingredient allergen notes with a single saved Boolean.

← Back to Prcatice more · Phase 1

Your assignment: Cafe Menu Board

Display three sample cafe items using one reusable card function. Add a basic Show/Hide button to reveal ingredient allergen notes with a single saved Boolean.

Phase 1 · Simple Compose practice

Build one screen using layouts, text, buttons, local resources and small saved state values. These projects use no animation APIs, navigation, databases or background work.

Beginner · 0.5–1 hours of building

The download is a complete reference app. Read the brief, predict the result, then implement each step in a separate working copy. Code runs in Android Studio on a device or emulator; the website displays and explains the source.

Before you begin

  • Create and run an Empty Activity project in Android Studio.
  • Read basic Kotlin functions, strings, and if expressions.
  • Complete the Phase 1 lessons on composables, layouts, modifiers, and basic state.

By the end, you can…

  • Render repeated content with composable parameters.
  • Use one Boolean to control several pieces of conditional text.
  • Keep a button label consistent with its next action.
  • Restore the details visibility after rotation.

Required features

  • Show Cheese sandwich at $4.50, Berry yogurt at $3.00, and Lemon tea at $2.00.
  • Display a description for each item with no ordering or checkout flow.
  • Hide the sample allergen details initially.
  • Use one button to show or hide the details for all three items.
  • Restore the visibility choice after rotation and keep all items scrollable.

Set up the project and run the reference app

  1. Open: Extract the ZIP. In Android Studio choose Open, then select the project folder containing settings.gradle.kts. Open that folder rather than just a Kotlin file.
  2. Prepare: Use Android Studio’s bundled JDK 17 or 21 in Gradle settings. In SDK Manager install Android SDK Platform 36 and any Build Tools requested during sync.
  3. Sync: Allow Gradle to download the pinned dependencies. Initial setup requires internet. The app runs offline and needs no backend, login or API key.
  4. Run: In Device Manager create an emulator with API 23 or newer, or connect a device with USB debugging. Select the app run configuration and press Run.
  5. Observe: Try the normal and boundary scenarios from Test the app. Record what changes after each action. Use Logcat to investigate a crash instead of changing several files at once.
  6. Practice: Copy the extracted folder to a working folder. Keep the build files, manifest, resources and CommonUi.kt. Rebuild the application files following the steps below. Attempt each task before consulting its reference.
PowerShell — in the project folder
.\gradlew.bat :app:assembleDebug :app:testDebugUnitTest
macOS/Linux: chmod +x gradlew, then ./gradlew :app:assembleDebug :app:testDebugUnitTest. Unit tests run without a device; manual UI checks need a device or emulator.

Keep the toolchain consistent

The projects use the existing course’s pinned toolchain: Kotlin 2.2.21, AGP 8.13.2, Gradle 8.13, and Compose BOM 2025.12.01. This app uses basic Compose UI. Its Gradle files include no animation, navigation, database or ViewModel libraries. All build files and the Gradle wrapper are included. Open TOOLCHAIN.md for setup details.

Understand the topics before coding

For each topic, connect the Kotlin concept to a visible behavior. Identify the value the screen reads and the event that can change that value.

TOPIC 01

Reusable menu cards

MenuItem receives a name, price, description, allergen note, and visibility flag. It owns the layout but does not own the show/hide state. Three calls can therefore share the same layout and respond to the same screen choice.

TOPIC 02

Conditional content

The if (showAllergens) block includes an extra Text only when the Boolean is true. Changing the flag changes the composed content immediately; this project uses no animation.

TOPIC 03

A descriptive button label

When details are hidden, the button says Show allergen details. When they are visible, it says Hide allergen details. Reading the same Boolean for both the label and the item visibility keeps the next action clear.

State and screen updates

When a saved state value changes, Compose updates the content that reads it. rememberSaveable can restore small values after Activity recreation, such as rotation. It does not permanently save your choices. Static content needs no mutable state.

Build the app step by step

Work in this order. The code panels are focused excerpts from the complete files below. They may begin or end inside a function: use the full source browser when copying a file, including its imports. After every step, run the app and check the stated result.

Step 1. Open the menu screen

Your task: Run MainActivity and follow its setContent call into MenuBoard inside MiniScreen.

Open: app/src/main/java/com/mastercoding/miniprojects/menu/MainActivity.kt

How it works: The shared host provides vertical scrolling, so the three menu cards do not require a lazy list or another screen at this size.

MainActivity.kt
import androidx.compose.ui.unit.dp

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent { MiniTheme { MiniScreen("Cafe Menu Board") { MenuBoard() } } }
    }
}

@Composable
fun MenuBoard() {
    var showAllergens by rememberSaveable { mutableStateOf(false) }
    Text("A small sample cafe menu", style = MaterialTheme.typography.titleLarge)
    Text("Prices are in USD. This screen displays a menu; it does not place orders.")
    Button(onClick = { showAllergens = !showAllergens }, modifier = Modifier.fillMaxWidth()) {
        Text(if (showAllergens) "Hide allergen details" else "Show allergen details")
    }
    MenuItem("Cheese sandwich", "$4.50", "Toasted bread with cheese and tomato.", "Milk and wheat", showAllergens)
    MenuItem("Berry yogurt", "$3.00", "Plain yogurt topped with mixed berries.", "Milk", showAllergens)
    MenuItem("Lemon tea", "$2.00", "Warm tea with a slice of lemon.", "No listed allergens in this sample recipe", showAllergens)
    Text("Sample recipe information for this exercise. A real cafe must verify its own ingredients and preparation.")
}

@Composable
private fun MenuItem(name: String, price: String, description: String, allergens: String, showAllergens: Boolean) {
    Card(modifier = Modifier.fillMaxWidth()) {
Reference excerpt · lines 21–46. The full file, with imports, is in Complete source files.

Stop and check

The Cafe Menu Board title and the three sample items appear.

Step 2. Create the visibility flag

Your task: Declare showAllergens with rememberSaveable and an initial false value inside MenuBoard.

Open: app/src/main/java/com/mastercoding/miniprojects/menu/MainActivity.kt

How it works: The screen owns one choice that affects all cards. A saved Boolean is sufficient for this small restoration requirement; no separate flag per menu item is needed.

MainActivity.kt
@Composable
fun MenuBoard() {
    var showAllergens by rememberSaveable { mutableStateOf(false) }
    Text("A small sample cafe menu", style = MaterialTheme.typography.titleLarge)
    Text("Prices are in USD. This screen displays a menu; it does not place orders.")
    Button(onClick = { showAllergens = !showAllergens }, modifier = Modifier.fillMaxWidth()) {
        Text(if (showAllergens) "Hide allergen details" else "Show allergen details")
    }
    MenuItem("Cheese sandwich", "$4.50", "Toasted bread with cheese and tomato.", "Milk and wheat", showAllergens)
    MenuItem("Berry yogurt", "$3.00", "Plain yogurt topped with mixed berries.", "Milk", showAllergens)
    MenuItem("Lemon tea", "$2.00", "Warm tea with a slice of lemon.", "No listed allergens in this sample recipe", showAllergens)
    Text("Sample recipe information for this exercise. A real cafe must verify its own ingredients and preparation.")
}

@Composable
private fun MenuItem(name: String, price: String, description: String, allergens: String, showAllergens: Boolean) {
    Card(modifier = Modifier.fillMaxWidth()) {
        Column(
            modifier = Modifier.padding(20.dp),
            verticalArrangement = Arrangement.spacedBy(8.dp)
        ) {
            Text(name, style = MaterialTheme.typography.titleMedium)
            Text(price, style = MaterialTheme.typography.labelLarge)
            Text(description)
            if (showAllergens) {
                Text("Allergen details: $allergens")
Reference excerpt · lines 30–55. The full file, with imports, is in Complete source files.

Stop and check

The initial screen hides all item allergen notes.

Step 3. Connect the show and hide action

Your task: Add a full-width Button that flips showAllergens. Derive Show allergen details or Hide allergen details from the same flag.

Open: app/src/main/java/com/mastercoding/miniprojects/menu/MainActivity.kt

How it works: The callback assigns the opposite Boolean. Compose reads the new flag in both the button label and each menu card, keeping the control and displayed content aligned.

MainActivity.kt
    Text("A small sample cafe menu", style = MaterialTheme.typography.titleLarge)
    Text("Prices are in USD. This screen displays a menu; it does not place orders.")
    Button(onClick = { showAllergens = !showAllergens }, modifier = Modifier.fillMaxWidth()) {
        Text(if (showAllergens) "Hide allergen details" else "Show allergen details")
    }
    MenuItem("Cheese sandwich", "$4.50", "Toasted bread with cheese and tomato.", "Milk and wheat", showAllergens)
    MenuItem("Berry yogurt", "$3.00", "Plain yogurt topped with mixed berries.", "Milk", showAllergens)
    MenuItem("Lemon tea", "$2.00", "Warm tea with a slice of lemon.", "No listed allergens in this sample recipe", showAllergens)
    Text("Sample recipe information for this exercise. A real cafe must verify its own ingredients and preparation.")
}

@Composable
private fun MenuItem(name: String, price: String, description: String, allergens: String, showAllergens: Boolean) {
    Card(modifier = Modifier.fillMaxWidth()) {
        Column(
            modifier = Modifier.padding(20.dp),
            verticalArrangement = Arrangement.spacedBy(8.dp)
        ) {
            Text(name, style = MaterialTheme.typography.titleMedium)
            Text(price, style = MaterialTheme.typography.labelLarge)
            Text(description)
            if (showAllergens) {
                Text("Allergen details: $allergens")
            }
        }
    }
Reference excerpt · lines 33–58. The full file, with imports, is in Complete source files.

Stop and check

Each tap changes the visibility choice and the button describes the next action.

Step 4. Build the reusable item card

Your task: Implement MenuItem with five parameters. Use a padded Card and Column to display name, price, and description.

Open: app/src/main/java/com/mastercoding/miniprojects/menu/MainActivity.kt

How it works: A vertical item layout lets names and descriptions wrap naturally. The helper displays supplied data without knowing which cafe item the caller chose.

MainActivity.kt

@Composable
private fun MenuItem(name: String, price: String, description: String, allergens: String, showAllergens: Boolean) {
    Card(modifier = Modifier.fillMaxWidth()) {
        Column(
            modifier = Modifier.padding(20.dp),
            verticalArrangement = Arrangement.spacedBy(8.dp)
        ) {
            Text(name, style = MaterialTheme.typography.titleMedium)
            Text(price, style = MaterialTheme.typography.labelLarge)
            Text(description)
            if (showAllergens) {
                Text("Allergen details: $allergens")
            }
        }
    }
}

@Preview(showBackground = true)
@Composable
private fun MenuBoardPreview() {
    MiniTheme { MiniScreen("Cafe Menu Board") { MenuBoard() } }
}
Reference excerpt · lines 43–66. The full file, with imports, is in Complete source files.

Stop and check

Every item shows its supplied name, price, and description in a consistent card.

Step 5. Pass the shared flag to each item

Your task: Call MenuItem for the sandwich, yogurt, and tea. Pass showAllergens to each call and include the extra Text inside if (showAllergens).

Open: app/src/main/java/com/mastercoding/miniprojects/menu/MainActivity.kt

How it works: All cards receive the same current flag. Showing details once therefore reveals the sample note on every card, and hiding details removes all three notes together.

MainActivity.kt
            Text(price, style = MaterialTheme.typography.labelLarge)
            Text(description)
            if (showAllergens) {
                Text("Allergen details: $allergens")
            }
        }
    }
}

@Preview(showBackground = true)
@Composable
private fun MenuBoardPreview() {
    MiniTheme { MiniScreen("Cafe Menu Board") { MenuBoard() } }
}
Reference excerpt · lines 52–66. The full file, with imports, is in Complete source files.

Stop and check

The sandwich note says Milk and wheat, the yogurt note says Milk, and the tea note reports no listed allergens in the sample recipe.

Step 6. Preview and verify the visibility choice

Your task: Open MenuBoardPreview. On a device, show the details, rotate, and then hide them again. Test larger fonts and scroll through all cards.

Open: app/src/main/java/com/mastercoding/miniprojects/menu/MainActivity.kt

How it works: The preview checks the static arrangement. Device rotation verifies saved state, while the second tap verifies that the restored flag still controls every item.

MainActivity.kt
@Preview(showBackground = true)
@Composable
private fun MenuBoardPreview() {
    MiniTheme { MiniScreen("Cafe Menu Board") { MenuBoard() } }
}
Reference excerpt · lines 61–66. The full file, with imports, is in Complete source files.

Stop and check

Visible details remain visible after rotation; hiding them removes each item note while keeping the menu usable.

Complete source files

Every text file in the ZIP is available here, including Kotlin, resources, Gradle scripts and the student README. The binary Gradle wrapper JAR is included in the ZIP. Source excerpts and downloads are generated from these same files.

Start hereResponsibility
MainActivity.ktThe launcher Activity installs the Compose UI with setContent.
CommonUi.ktMaterial 3 theme and reusable screen container with scrolling and system insets.
Application Kotlin filesLayouts, button callbacks and simple saved state introduced in the guide.
app/src/main/resImages, text resources, and the launch theme.
app/build.gradle.ktsSDK settings and the exact libraries used by this app.
README.md / TOOLCHAIN.mdStudent instructions and setup, usable without the website.

Android launcher Activity, setContent entry point and screen composition. Start tracing the app here.

17 complete text files. Copy the selected file with the code panel button, or download the full project above.

MainActivity.kt
package com.mastercoding.miniprojects.menu

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent { MiniTheme { MiniScreen("Cafe Menu Board") { MenuBoard() } } }
    }
}

@Composable
fun MenuBoard() {
    var showAllergens by rememberSaveable { mutableStateOf(false) }
    Text("A small sample cafe menu", style = MaterialTheme.typography.titleLarge)
    Text("Prices are in USD. This screen displays a menu; it does not place orders.")
    Button(onClick = { showAllergens = !showAllergens }, modifier = Modifier.fillMaxWidth()) {
        Text(if (showAllergens) "Hide allergen details" else "Show allergen details")
    }
    MenuItem("Cheese sandwich", "$4.50", "Toasted bread with cheese and tomato.", "Milk and wheat", showAllergens)
    MenuItem("Berry yogurt", "$3.00", "Plain yogurt topped with mixed berries.", "Milk", showAllergens)
    MenuItem("Lemon tea", "$2.00", "Warm tea with a slice of lemon.", "No listed allergens in this sample recipe", showAllergens)
    Text("Sample recipe information for this exercise. A real cafe must verify its own ingredients and preparation.")
}

@Composable
private fun MenuItem(name: String, price: String, description: String, allergens: String, showAllergens: Boolean) {
    Card(modifier = Modifier.fillMaxWidth()) {
        Column(
            modifier = Modifier.padding(20.dp),
            verticalArrangement = Arrangement.spacedBy(8.dp)
        ) {
            Text(name, style = MaterialTheme.typography.titleMedium)
            Text(price, style = MaterialTheme.typography.labelLarge)
            Text(description)
            if (showAllergens) {
                Text("Allergen details: $allergens")
            }
        }
    }
}

@Preview(showBackground = true)
@Composable
private fun MenuBoardPreview() {
    MiniTheme { MiniScreen("Cafe Menu Board") { MenuBoard() } }
}

Test the app with concrete scenarios

Run these scenarios on your device or emulator. Write down the actual result beside the expected result. A successful Gradle build checks compilation; it does not demonstrate tapping, accessibility, keyboard behavior or rotation.

Do thisExpected result
Launch the app.Three sample items and USD prices appear, with the item allergen notes hidden.
Tap Show allergen details.The sandwich, yogurt, and tea each show their supplied sample allergen note.
Read the button after showing details.It says Hide allergen details.
Tap Hide allergen details.All three item notes disappear together; names, prices, and descriptions remain.
Show details and rotate the device.The details remain visible and the button still says Hide allergen details.
Use a large font on a small screen.Item text wraps and all three cards and the final sample-data note remain reachable by scrolling.

Your completion checklist

Project acceptance checklist0 / 5 verified

Check an item only after demonstrating it. Progress is saved in this browser.

Troubleshooting: understand the cause

Only one item reveals its note.

Cause: Different visibility values were passed to different MenuItem calls.

Repair: Pass the same screen-owned showAllergens value to each item.

The button says Show even when details are visible.

Cause: The button text is hard-coded.

Repair: Choose the button text with the same showAllergens Boolean that controls the item notes.

Submit your app and explain your decisions

  1. Submit the Android Studio project without build caches, .gradle or machine-specific local.properties.
  2. Include two screenshots demonstrating normal behavior and an edge case.
  3. Add a short README recording the device/API version, actions, observed outcomes, and any known limitation.
  4. Answer the questions below using function and state names from your own implementation.
  • Which function owns showAllergens, and which functions receive it?
  • How does changing one Boolean update three menu cards?
  • Why is a regular scrollable Column sufficient for three fixed items?
CriterionPointsWhat to demonstrate
Core behavior40All required actions give the expected result.
Validation and lifecycle20Invalid inputs, empty states, rotation and the project's persistence rules work.
Interface and accessibility20Readable contrast, labeled inputs, meaningful image descriptions, scrollable content and usable touch targets.
Code and explanation20Small named functions, clear state ownership and a correct explanation of one event-to-result trace.

Extra challenges after the core works

  • Add a fourth sample MenuItem call using the same function.
  • Add a static opening-hours section below the menu using Text and Column.

Choose one extension, write its acceptance check first, then implement it without breaking the original scenarios.

Further reading