← 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
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…
- 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
- 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.
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.
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.
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
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.
@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")
}Stop and check
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.
@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.")Stop and check
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.
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.")
}Stop and check
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.
.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() } }
}Stop and check
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.
)
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() } }
}Stop and check
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.
}
}
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() } }
}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.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 this | Expected 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
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
- 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.
- 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?
| 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 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.