Phase 1 · Getting Started with ComposeProject 1

Greeting Card

Arrange a greeting card, choose a preset recipient with ordinary buttons, and reset the greeting. Practice text, spacing, simple callbacks, and one saved string on a single screen.

← Back to Prcatice more · Phase 1

Your assignment: Greeting Card

Arrange a greeting card, choose a preset recipient with ordinary buttons, and reset the greeting. Practice text, spacing, simple callbacks, and one saved string on a single screen.

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…

  • Arrange related text inside a padded Card and Column.
  • Connect ordinary buttons to one saved recipient string.
  • Derive a greeting directly from current state.
  • Preview the screen and verify that the recipient survives rotation.

Required features

  • Show a greeting, a short message, and the sender Alex.
  • Offer friend, teacher, and family as preset recipient choices.
  • Update the greeting immediately after a recipient button tap.
  • Reset the recipient to friend with a labelled button.
  • Restore the chosen recipient after rotation and keep the whole screen 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

Text and layout

The card groups the heading, message, and sender in a Column. Padding creates space inside the card; spacedBy keeps the text elements apart. Typography gives the greeting more emphasis without changing how the screen behaves.

TOPIC 02

One saved string

recipient is the only changing value. Each button assigns a preset word, and the heading reads that same word. rememberSaveable restores the small string when Android recreates the activity after rotation; it does not create permanent profile storage.

TOPIC 03

An event changes state

Tapping For a teacher runs its onClick callback, which assigns teacher to recipient. Compose then reads the new value and displays Hello, teacher! The heading is derived from recipient rather than maintained as a second mutable value.

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. Run the activity and screen host

Your task: Open MainActivity.kt and follow onCreate through setContent, MiniTheme, and MiniScreen. Run this host before changing the card.

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

How it works: The activity supplies the Android entry point. MiniScreen supplies the title, scrolling, and shared spacing, leaving GreetingCard responsible for its own content and recipient state.

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

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent { MiniTheme { MiniScreen("Greeting Card") { GreetingCard() } } }
    }
}

@Composable
fun GreetingCard() {
    var recipient by rememberSaveable { mutableStateOf("friend") }

    Card(modifier = Modifier.fillMaxWidth()) {
        Column(
            modifier = Modifier.padding(24.dp),
            verticalArrangement = Arrangement.spacedBy(12.dp)
        ) {
            Text("Hello, $recipient!", style = MaterialTheme.typography.headlineMedium)
            Text("Wishing you a wonderful day.", style = MaterialTheme.typography.bodyLarge)
            Text("From Alex", style = MaterialTheme.typography.labelLarge)
        }
    }
    Text("Choose who receives the greeting. Current recipient: $recipient.")
    Button(onClick = { recipient = "friend" }, modifier = Modifier.fillMaxWidth()) {
        Text("For a friend")
Reference excerpt · lines 21–46. The full file, with imports, is in Complete source files.

Stop and check

The app opens with the Greeting Card title and the greeting content.

Step 2. Declare the recipient state

Your task: Inside GreetingCard, create recipient with rememberSaveable and the initial value friend.

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

How it works: A regular local string would be recreated as the composable executes. Saved observable state lets button callbacks change the value and lets the recreated activity restore the current preset.

MainActivity.kt
@Composable
fun GreetingCard() {
    var recipient by rememberSaveable { mutableStateOf("friend") }

    Card(modifier = Modifier.fillMaxWidth()) {
        Column(
            modifier = Modifier.padding(24.dp),
            verticalArrangement = Arrangement.spacedBy(12.dp)
        ) {
            Text("Hello, $recipient!", style = MaterialTheme.typography.headlineMedium)
            Text("Wishing you a wonderful day.", style = MaterialTheme.typography.bodyLarge)
            Text("From Alex", style = MaterialTheme.typography.labelLarge)
        }
    }
    Text("Choose who receives the greeting. Current recipient: $recipient.")
    Button(onClick = { recipient = "friend" }, modifier = Modifier.fillMaxWidth()) {
        Text("For a friend")
    }
    Button(onClick = { recipient = "teacher" }, modifier = Modifier.fillMaxWidth()) {
        Text("For a teacher")
    }
    Button(onClick = { recipient = "family" }, modifier = Modifier.fillMaxWidth()) {
        Text("For family")
    }
    Button(onClick = { recipient = "friend" }, modifier = Modifier.fillMaxWidth()) {
        Text("Reset greeting")
Reference excerpt · lines 30–55. The full file, with imports, is in Complete source files.

Stop and check

The initial greeting uses friend, and there is one owner for the selected recipient.

Step 3. Arrange the greeting card

Your task: Add a full-width Card containing a padded Column. Display the greeting with recipient, the fixed message, and From Alex using three Text calls.

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

How it works: Only the recipient changes. The sender and message remain ordinary constants, so the first version needs no input fields, data model, or storage layer.

MainActivity.kt
            verticalArrangement = Arrangement.spacedBy(12.dp)
        ) {
            Text("Hello, $recipient!", style = MaterialTheme.typography.headlineMedium)
            Text("Wishing you a wonderful day.", style = MaterialTheme.typography.bodyLarge)
            Text("From Alex", style = MaterialTheme.typography.labelLarge)
        }
    }
    Text("Choose who receives the greeting. Current recipient: $recipient.")
    Button(onClick = { recipient = "friend" }, modifier = Modifier.fillMaxWidth()) {
        Text("For a friend")
    }
    Button(onClick = { recipient = "teacher" }, modifier = Modifier.fillMaxWidth()) {
        Text("For a teacher")
    }
    Button(onClick = { recipient = "family" }, modifier = Modifier.fillMaxWidth()) {
        Text("For family")
    }
    Button(onClick = { recipient = "friend" }, modifier = Modifier.fillMaxWidth()) {
        Text("Reset greeting")
    }
    Text("The recipient survives rotation. This practice card is displayed only inside the app.")
}

@Preview(showBackground = true)
@Composable
private fun GreetingCardPreview() {
Reference excerpt · lines 37–62. The full file, with imports, is in Complete source files.

Stop and check

The card shows Hello, friend!, the message, and From Alex with consistent spacing.

Step 4. Connect the preset buttons

Your task: Add the three full-width recipient Buttons. In the teacher button callback, assign teacher to recipient; do the equivalent for friend and family.

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

How it works: Each click changes the same saved string. The card and current-recipient sentence both read that value, so the display stays consistent without manually updating separate Text elements.

MainActivity.kt
        Text("For a friend")
    }
    Button(onClick = { recipient = "teacher" }, modifier = Modifier.fillMaxWidth()) {
        Text("For a teacher")
    }
    Button(onClick = { recipient = "family" }, modifier = Modifier.fillMaxWidth()) {
        Text("For family")
    }
    Button(onClick = { recipient = "friend" }, modifier = Modifier.fillMaxWidth()) {
        Text("Reset greeting")
    }
    Text("The recipient survives rotation. This practice card is displayed only inside the app.")
}

@Preview(showBackground = true)
@Composable
private fun GreetingCardPreview() {
    MiniTheme { MiniScreen("Greeting Card") { GreetingCard() } }
}
Reference excerpt · lines 46–65. The full file, with imports, is in Complete source files.

Stop and check

For a teacher immediately changes the heading to Hello, teacher!; the other buttons select their own presets.

Step 5. Reset and check restoration

Your task: Add Reset greeting and assign friend in its callback. Select family, rotate the device, then press Reset greeting.

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

How it works: Reset is another state assignment and uses the same path as choosing a preset. Rotation checks saved instance state; resetting checks that the app can return to its initial display.

MainActivity.kt
    }
    Button(onClick = { recipient = "friend" }, modifier = Modifier.fillMaxWidth()) {
        Text("Reset greeting")
    }
    Text("The recipient survives rotation. This practice card is displayed only inside the app.")
}

@Preview(showBackground = true)
@Composable
private fun GreetingCardPreview() {
    MiniTheme { MiniScreen("Greeting Card") { GreetingCard() } }
}
Reference excerpt · lines 53–65. The full file, with imports, is in Complete source files.

Stop and check

Family survives rotation; Reset greeting returns the card to Hello, friend!.

Step 6. Inspect the preview and long-content layout

Your task: Open GreetingCardPreview in Android Studio and run the app with a larger system font. Scroll to reach the final button and explanation.

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

How it works: The preview reuses the same theme, screen host, and card function. The supplied scrolling host accommodates content that becomes taller when the font grows.

MainActivity.kt
@Preview(showBackground = true)
@Composable
private fun GreetingCardPreview() {
    MiniTheme { MiniScreen("Greeting Card") { GreetingCard() } }
}
Reference excerpt · lines 60–65. The full file, with imports, is in Complete source files.

Stop and check

The preview renders the card, and all buttons remain reachable on the device at larger font sizes.

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.greeting

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("Greeting Card") { GreetingCard() } } }
    }
}

@Composable
fun GreetingCard() {
    var recipient by rememberSaveable { mutableStateOf("friend") }

    Card(modifier = Modifier.fillMaxWidth()) {
        Column(
            modifier = Modifier.padding(24.dp),
            verticalArrangement = Arrangement.spacedBy(12.dp)
        ) {
            Text("Hello, $recipient!", style = MaterialTheme.typography.headlineMedium)
            Text("Wishing you a wonderful day.", style = MaterialTheme.typography.bodyLarge)
            Text("From Alex", style = MaterialTheme.typography.labelLarge)
        }
    }
    Text("Choose who receives the greeting. Current recipient: $recipient.")
    Button(onClick = { recipient = "friend" }, modifier = Modifier.fillMaxWidth()) {
        Text("For a friend")
    }
    Button(onClick = { recipient = "teacher" }, modifier = Modifier.fillMaxWidth()) {
        Text("For a teacher")
    }
    Button(onClick = { recipient = "family" }, modifier = Modifier.fillMaxWidth()) {
        Text("For family")
    }
    Button(onClick = { recipient = "friend" }, modifier = Modifier.fillMaxWidth()) {
        Text("Reset greeting")
    }
    Text("The recipient survives rotation. This practice card is displayed only inside the app.")
}

@Preview(showBackground = true)
@Composable
private fun GreetingCardPreview() {
    MiniTheme { MiniScreen("Greeting Card") { GreetingCard() } }
}

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.Hello, friend!, the fixed message, and From Alex are visible.
Tap For a teacher, then For family.The heading and current-recipient sentence change to teacher and then family.
Tap For a friend several times.The recipient remains friend; repeated taps do not add duplicate content.
Choose family, then rotate the device.Family remains the recipient after activity recreation.
Tap Reset greeting after selecting teacher.The greeting returns to Hello, friend!.
Use a small screen, a large font, and TalkBack.The screen can scroll, the greeting is readable, and every button has a meaningful spoken label.

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

The heading always says friend.

Cause: The heading uses a fixed string instead of reading recipient.

Repair: Interpolate recipient in the greeting Text and assign to that variable in each callback.

The recipient is lost after rotation.

Cause: The value is a normal variable or uses only remember.

Repair: Use rememberSaveable for this small string and verify restoration by rotating after choosing a different preset.

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.
  • Trace a teacher button tap through the callback, state assignment, and greeting text.
  • Why does the greeting not need a second mutable string?
  • How does saved state after rotation differ from permanent storage?
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 one more preset recipient using an ordinary Button.
  • Change the static message and sender in the source, then compare the preview with the running app.

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

Further reading