Phase 1 · Getting Started with ComposeProject 5

Color Palette

Choose Indigo, Teal, or Amber with ordinary buttons and display the matching color sample. Practice a when expression, modifiers, and text that identifies the current choice.

← Back to Prcatice more · Phase 1

Your assignment: Color Palette

Choose Indigo, Teal, or Amber with ordinary buttons and display the matching color sample. Practice a when expression, modifiers, and text that identifies the current choice.

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…

  • Keep one selected name in saved state.
  • Derive a Color with a simple Kotlin when expression.
  • Use background, border, width, and height modifiers on a Box.
  • Show the selection in words as well as color.

Required features

  • Start with Indigo and offer Indigo, Teal, and Amber buttons.
  • Display a large sample of the selected color with a visible boundary.
  • Show the selected name above the sample and in the selected button label.
  • Reset the selection to Indigo.
  • Restore the choice after rotation and expose a useful sample description for accessibility.

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

One choice, one string

selectedName is the only changing value. The title, sample color, and button labels read this value. Using one string prevents several independent Booleans from claiming that more than one color is selected.

TOPIC 02

Derived color

A when expression maps Teal to one Color, Amber to another, and the default Indigo to its own Color. The color is calculated from the selected name instead of stored as a second mutable value.

TOPIC 03

Selection in words

The screen prints Selected color: Amber and adds selected to the Amber button label. The sample also has a spoken content description. This makes the current choice understandable without relying only on visual color differences.

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 palette screen

Your task: Run MainActivity and trace setContent into ColorPalette inside the supplied theme and scrollable MiniScreen.

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

How it works: The project has one screen and no color-picker library. It teaches a small preset choice using ordinary buttons and a Box.

MainActivity.kt

@Composable
fun ColorPalette() {
    var selectedName by rememberSaveable { mutableStateOf("Indigo") }
    val swatchColor = when (selectedName) {
        "Teal" -> Color(0xFF00796B)
        "Amber" -> Color(0xFFFFC107)
        else -> Color(0xFF3949AB)
    }

    Text("Selected color: $selectedName", style = MaterialTheme.typography.titleLarge)
    Box(
        modifier = Modifier.fillMaxWidth().height(120.dp)
            .background(swatchColor)
            .border(1.dp, MaterialTheme.colorScheme.onSurface)
            .semantics { contentDescription = "$selectedName color sample" }
    )
    Text("Choose a named color. The label and sample always show the same choice.")
    listOf("Indigo", "Teal", "Amber").forEach { name ->
        Button(onClick = { selectedName = name }, modifier = Modifier.fillMaxWidth()) {
            Text(if (selectedName == name) "$name (selected)" else name)
        }
    }
    Button(onClick = { selectedName = "Indigo" }, modifier = Modifier.fillMaxWidth()) {
        Text("Reset to Indigo")
    }
Reference excerpt · lines 32–57. The full file, with imports, is in Complete source files.

Stop and check

The app opens with the Color Palette title and the Indigo sample.

Step 2. Save the selected name

Your task: Declare selectedName with rememberSaveable and the initial string Indigo.

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

How it works: A small string can be restored when Android recreates the activity. This one state value will drive every part of the displayed selection.

MainActivity.kt
@Composable
fun ColorPalette() {
    var selectedName by rememberSaveable { mutableStateOf("Indigo") }
    val swatchColor = when (selectedName) {
        "Teal" -> Color(0xFF00796B)
        "Amber" -> Color(0xFFFFC107)
        else -> Color(0xFF3949AB)
    }

    Text("Selected color: $selectedName", style = MaterialTheme.typography.titleLarge)
    Box(
        modifier = Modifier.fillMaxWidth().height(120.dp)
            .background(swatchColor)
            .border(1.dp, MaterialTheme.colorScheme.onSurface)
            .semantics { contentDescription = "$selectedName color sample" }
    )
    Text("Choose a named color. The label and sample always show the same choice.")
    listOf("Indigo", "Teal", "Amber").forEach { name ->
        Button(onClick = { selectedName = name }, modifier = Modifier.fillMaxWidth()) {
            Text(if (selectedName == name) "$name (selected)" else name)
        }
    }
    Button(onClick = { selectedName = "Indigo" }, modifier = Modifier.fillMaxWidth()) {
        Text("Reset to Indigo")
    }
    Text("Names and selected labels identify the choice without relying on color alone. The choice survives rotation.")
Reference excerpt · lines 33–58. The full file, with imports, is in Complete source files.

Stop and check

The initial selected name is Indigo.

Step 3. Map the name to a color

Your task: Write the supplied when expression with Teal and Amber branches and an Indigo default branch.

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

How it works: The selected color is derived data. Tapping Teal changes selectedName, which causes the when expression to produce the Teal color on the next composition.

MainActivity.kt
fun ColorPalette() {
    var selectedName by rememberSaveable { mutableStateOf("Indigo") }
    val swatchColor = when (selectedName) {
        "Teal" -> Color(0xFF00796B)
        "Amber" -> Color(0xFFFFC107)
        else -> Color(0xFF3949AB)
    }

    Text("Selected color: $selectedName", style = MaterialTheme.typography.titleLarge)
    Box(
        modifier = Modifier.fillMaxWidth().height(120.dp)
            .background(swatchColor)
            .border(1.dp, MaterialTheme.colorScheme.onSurface)
            .semantics { contentDescription = "$selectedName color sample" }
    )
    Text("Choose a named color. The label and sample always show the same choice.")
    listOf("Indigo", "Teal", "Amber").forEach { name ->
        Button(onClick = { selectedName = name }, modifier = Modifier.fillMaxWidth()) {
            Text(if (selectedName == name) "$name (selected)" else name)
        }
    }
    Button(onClick = { selectedName = "Indigo" }, modifier = Modifier.fillMaxWidth()) {
        Text("Reset to Indigo")
    }
    Text("Names and selected labels identify the choice without relying on color alone. The choice survives rotation.")
}
Reference excerpt · lines 34–59. The full file, with imports, is in Complete source files.

Stop and check

Each supported name maps to its expected fixed color.

Step 4. Draw and describe the sample

Your task: Add a Box with full width, 120.dp height, the derived background color, and an onSurface border. Give it the selected-name content description.

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

How it works: The background displays the sample, while the border keeps its edge visible against the theme surface. The text above the Box and its accessibility description identify what the color means.

MainActivity.kt
            .background(swatchColor)
            .border(1.dp, MaterialTheme.colorScheme.onSurface)
            .semantics { contentDescription = "$selectedName color sample" }
    )
    Text("Choose a named color. The label and sample always show the same choice.")
    listOf("Indigo", "Teal", "Amber").forEach { name ->
        Button(onClick = { selectedName = name }, modifier = Modifier.fillMaxWidth()) {
            Text(if (selectedName == name) "$name (selected)" else name)
        }
    }
    Button(onClick = { selectedName = "Indigo" }, modifier = Modifier.fillMaxWidth()) {
        Text("Reset to Indigo")
    }
    Text("Names and selected labels identify the choice without relying on color alone. The choice survives rotation.")
}

@Preview(showBackground = true)
@Composable
private fun ColorPalettePreview() {
    MiniTheme { MiniScreen("Color Palette") { ColorPalette() } }
}
Reference excerpt · lines 45–66. The full file, with imports, is in Complete source files.

Stop and check

The sample is visible and a screen reader can identify it by its selected color name.

Step 5. Connect the named buttons

Your task: Loop over Indigo, Teal, and Amber to create full-width Buttons. Assign name in each callback and append selected to the current button label.

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

How it works: The three buttons receive different names but update the same string. Exactly one label matches that string, so one choice is identified at a time.

MainActivity.kt
    )
    Text("Choose a named color. The label and sample always show the same choice.")
    listOf("Indigo", "Teal", "Amber").forEach { name ->
        Button(onClick = { selectedName = name }, modifier = Modifier.fillMaxWidth()) {
            Text(if (selectedName == name) "$name (selected)" else name)
        }
    }
    Button(onClick = { selectedName = "Indigo" }, modifier = Modifier.fillMaxWidth()) {
        Text("Reset to Indigo")
    }
    Text("Names and selected labels identify the choice without relying on color alone. The choice survives rotation.")
}

@Preview(showBackground = true)
@Composable
private fun ColorPalettePreview() {
    MiniTheme { MiniScreen("Color Palette") { ColorPalette() } }
}
Reference excerpt · lines 48–66. The full file, with imports, is in Complete source files.

Stop and check

Tapping Amber changes the heading, sample, and Amber button label together.

Step 6. Reset, preview, and rotate

Your task: Add Reset to Indigo. Open ColorPalettePreview, then choose Teal on a device, rotate, and finally reset.

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

How it works: Reset is a simple state assignment. Rotation checks saved-name restoration; the derived color and labels should automatically follow the restored name.

MainActivity.kt
        }
    }
    Button(onClick = { selectedName = "Indigo" }, modifier = Modifier.fillMaxWidth()) {
        Text("Reset to Indigo")
    }
    Text("Names and selected labels identify the choice without relying on color alone. The choice survives rotation.")
}

@Preview(showBackground = true)
@Composable
private fun ColorPalettePreview() {
    MiniTheme { MiniScreen("Color Palette") { ColorPalette() } }
}
Reference excerpt · lines 53–66. The full file, with imports, is in Complete source files.

Stop and check

Teal survives rotation, and Reset to Indigo returns every selection indicator to Indigo.

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

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.material3.Button
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.graphics.Color
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
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("Color Palette") { ColorPalette() } } }
    }
}

@Composable
fun ColorPalette() {
    var selectedName by rememberSaveable { mutableStateOf("Indigo") }
    val swatchColor = when (selectedName) {
        "Teal" -> Color(0xFF00796B)
        "Amber" -> Color(0xFFFFC107)
        else -> Color(0xFF3949AB)
    }

    Text("Selected color: $selectedName", style = MaterialTheme.typography.titleLarge)
    Box(
        modifier = Modifier.fillMaxWidth().height(120.dp)
            .background(swatchColor)
            .border(1.dp, MaterialTheme.colorScheme.onSurface)
            .semantics { contentDescription = "$selectedName color sample" }
    )
    Text("Choose a named color. The label and sample always show the same choice.")
    listOf("Indigo", "Teal", "Amber").forEach { name ->
        Button(onClick = { selectedName = name }, modifier = Modifier.fillMaxWidth()) {
            Text(if (selectedName == name) "$name (selected)" else name)
        }
    }
    Button(onClick = { selectedName = "Indigo" }, modifier = Modifier.fillMaxWidth()) {
        Text("Reset to Indigo")
    }
    Text("Names and selected labels identify the choice without relying on color alone. The choice survives rotation.")
}

@Preview(showBackground = true)
@Composable
private fun ColorPalettePreview() {
    MiniTheme { MiniScreen("Color Palette") { ColorPalette() } }
}

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.Selected color: Indigo appears above the Indigo sample, and the Indigo button says selected.
Tap Teal, then Amber.The sample, heading, and selected button label change together for each choice.
Tap Amber again.Amber remains selected with no extra samples or duplicate content.
Choose Teal and rotate the device.Teal remains selected and its sample remains visible.
Tap Reset to Indigo.The sample and all text indicators return to Indigo.
Use TalkBack and a large font in light and dark system themes.The sample has a named description, the selected button is identifiable by its words, and all controls 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

The heading says Teal while the sample stays Indigo.

Cause: The Box background uses a fixed Color or separate mutable color state.

Repair: Use the swatchColor derived from selectedName as the background.

The current choice is clear only from the sample color.

Cause: The selection text and button feedback were omitted.

Repair: Keep Selected color above the sample, selected in the active button label, and the sample content description.

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.
  • Why save selectedName rather than keeping separate saved color and selected-name values?
  • What does the when expression return after a Teal button tap?
  • Which parts of the screen identify the selection without requiring the user to distinguish colors?
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 preset by extending both the button-name list and the when expression.
  • Extract the sample Box into a composable that receives a color and a description.

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

Further reading