← 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
Beginner · 0.5–1 hours of building
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
- 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. - 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.
- Sync: Allow Gradle to download the pinned dependencies. Initial setup requires internet. The app runs offline and needs no backend, login or API key.
- Run: In Device Manager create an emulator with API 23 or newer, or connect a device with USB debugging. Select the
apprun configuration and press Run. - 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.
- 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.
.\gradlew.bat :app:assembleDebug :app:testDebugUnitTestKeep the toolchain consistent
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.
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.
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.
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
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.
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()) {Stop and check
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.
@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")Stop and check
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.
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")
}
}
}Stop and check
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.
@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() } }
}Stop and check
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.
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() } }
}Stop and check
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.
@Preview(showBackground = true)
@Composable
private fun MenuBoardPreview() {
MiniTheme { MiniScreen("Cafe Menu Board") { MenuBoard() } }
}Stop and check
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 here | Responsibility |
|---|---|
| MainActivity.kt | The launcher Activity installs the Compose UI with setContent. |
| CommonUi.kt | Material 3 theme and reusable screen container with scrolling and system insets. |
| Application Kotlin files | Layouts, button callbacks and simple saved state introduced in the guide. |
| app/src/main/res | Images, text resources, and the launch theme. |
| app/build.gradle.kts | SDK settings and the exact libraries used by this app. |
| README.md / TOOLCHAIN.md | Student 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.
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 this | Expected 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
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
- Submit the Android Studio project without build caches,
.gradleor machine-specificlocal.properties. - Include two screenshots demonstrating normal behavior and an edge case.
- Add a short README recording the device/API version, actions, observed outcomes, and any known limitation.
- 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?
| Criterion | Points | What to demonstrate |
|---|---|---|
| Core behavior | 40 | All required actions give the expected result. |
| Validation and lifecycle | 20 | Invalid inputs, empty states, rotation and the project's persistence rules work. |
| Interface and accessibility | 20 | Readable contrast, labeled inputs, meaningful image descriptions, scrollable content and usable touch targets. |
| Code and explanation | 20 | Small 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.