Build a Knowledge Vault App for Android
Build a multi-screen Android app with Jetpack Compose, navigation, and state management.
Introduction
30 Second Summary
Think about how you organize your notes, photos, and important documents. Most of us have information scattered across apps and folders, making it hard to find what we need when we need it.
In this project, you will build a native Android app called Knowledge Vault that organizes information by topic and lets you tap into any file to view its content.
What You'll Build
A native Android app built with Kotlin and Jetpack Compose that displays topics in a scrollable list and opens file details when tapped.
By the end of this project, you'll have:
- A native Android app running on your phone or emulator.
- A home screen with topics and their files displayed in a scrollable layout using LazyColumn.
- A detail screen that opens when any file is tapped, rendering images or text content inline.
- Hardcoded sample data representing your vault structure with topics like "John F. Kennedy" and "Albert Einstein".
- Secret Mission: Add a category filter so you can view only images, only text files, or all files.
Are there any prerequisites?
No prior Android or Kotlin experience is required. This guide walks you through setting up all the tools you need from scratch, including Android Studio and a device to run your app on.
Set Up Your Android Project
To build the Knowledge Vault app, you need a properly configured Android project as your foundation. Android Studio provides a project template that sets up everything you need to start writing Jetpack Compose UI code right away.
In this step, you will create the project, add two libraries you will use later in the project, and confirm the app runs successfully on a device.
In this step, get ready to:
- Create a new Android project with the Empty Activity template.
- Add the Navigation Compose and Coil dependencies to your build file.
- Run the app on your device or emulator to confirm it works.
Create a new Android Studio project
The Empty Activity template gives you a Kotlin project with Jetpack Compose already configured. You just need to name it and pick the right minimum SDK level.
- On the Welcome to Android Studio screen, click New Project.
- Select the Phone and Tablet tab at the top.
- Click the Empty Activity template to select it.
- Click Next.
- In the Name field, enter KnowledgeVault.
- Leave the Package name as com.example.knowledgevault.
- From the Minimum SDK dropdown, select API 24: Android 7.0 (Nougat).
- Click Finish.
Android Studio will spend a moment building and syncing the project. Wait for the progress bar at the bottom to finish before continuing.
What is the Empty Activity template?
The Empty Activity template creates a single-screen Compose app with all the necessary dependencies already wired up. It includes Material Design 3 styling and a starter composable function that displays greeting text.
Because you chose this template, Kotlin is the only language available. Jetpack Compose requires Kotlin.
Project sync stuck or failing?
If Gradle sync fails with a network error, check that your internet connection is active. Android Studio needs to download dependencies during the initial sync.
If you see a warning about Android SDK licenses, click the link in the error message and accept the licenses.
Why is my Gradle sync failing?
Add the Navigation Compose and Coil dependencies
Your app will have two screens with navigation between them, and it will load images. Two libraries handle this: Navigation Compose for screen routing, and Coil for image loading.
- In the left sidebar of Android Studio, expand the Gradle Scripts section.
- Open the file labeled build.gradle.kts (Module :app).
- Scroll down to the dependencies { } block at the bottom of the file.
- Add these two lines inside the dependencies block, below the existing lines:
implementation("androidx.navigation:navigation-compose:2.9.8")
implementation("io.coil-kt.coil3:coil-compose:3.5.0")
What do these dependencies do?
Navigation Compose provides the NavHost and NavController you will use to switch between the home screen and the detail screen.
Coil is a lightweight image loading library designed for Kotlin. You are adding it now so you do not need a second Gradle sync later when you start working with images.
- After adding the two lines, click the Sync Now link that appears in the yellow notification bar at the top of the editor.
- Wait for the sync to complete. The progress bar at the bottom of Android Studio will disappear when it finishes.
Sync Now not appearing?
If you do not see the yellow bar, click the elephant icon (Gradle) in the toolbar, or go to File then Sync Project with Gradle Files.
If sync fails with a "Could not resolve" error, double check that the dependency strings match exactly. A typo in the group ID or version will cause the resolution to fail.
Why can't Gradle resolve my dependencies?
✔️ Awesome, I've got everything!
Great. Double check you have saved the file, then move on to the next substep.
ⓧ I'd like to double check the full code
Your full dependencies block should look like this:
dependencies {
implementation(platform("androidx.compose:compose-bom:2026.06.00"))
implementation("androidx.compose.material3:material3")
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.activity:activity-compose:1.13.0")
implementation("androidx.navigation:navigation-compose:2.9.8")
implementation("io.coil-kt.coil3:coil-compose:3.5.0")
debugImplementation("androidx.compose.ui:ui-tooling")
}
Run the app on your device or emulator
Before writing any custom code, confirm that the template project builds and launches correctly. This catches environment issues early.
- Select your target device from the device dropdown in the top toolbar. This is either your connected physical phone or an emulator you have already created.
- Click the green Run button (the play triangle) in the toolbar, or press Shift+F10 (Windows) / Ctrl+R (macOS).
- Wait for the build to finish and the app to launch on your device.
You should see a screen with the text "Hello Android!" displayed on a white background.
What should I see?
The default Empty Activity template displays a simple greeting. If you see any text on screen, your project is configured correctly. You will replace this content with your vault UI in the next steps.
App not launching?
If you are using a physical device, make sure USB debugging is enabled in your phone's Developer Options.
If the emulator is not appearing in the device dropdown, open Device Manager from the right sidebar and create a new virtual device.
My app is not launching on my device or emulator. Help me troubleshoot.
Your project is ready to go. Next up, you will define the data model that represents topics and files in your vault.
Define Your Vault Data Model
Your app is running and ready for code. Before building any screens, you need to define the shape of the data your app will display.
Every app needs a clear data model that describes its core objects. In this step, you'll create Kotlin data classes that represent topics and files in your vault, then fill them with sample content about historical figures.
In this step, get ready to:
- Define data classes for topics and files with a file type enum.
- Create a sample data object with three historical topics and their associated files.
- Add placeholder images to your project's drawable resources.
Create the VaultData.kt file with type definitions
Your vault organizes information into topics, and each topic contains files. You need classes that represent both of these concepts, plus a way to distinguish between image files and text files.
- In the Project window on the left side of Android Studio, expand app > java > com.example.knowledgevault.
- Right-click the com.example.knowledgevault package.
- Select New > Kotlin Class/File.
- In the dialog, select File and type VaultData as the name.
- Press Enter to create the file.
- Replace any generated content in the file with the following code:
package com.example.knowledgevault
// Defines the two types of files the vault can hold
enum class FileType {
IMAGE,
TEXT
}
// Represents a single file inside a topic
data class VaultFile(
val id: String,
val topicId: String,
val fileName: String,
val fileType: FileType,
val content: String
)
// Represents a topic that groups related files together
data class VaultTopic(
val id: String,
val name: String,
val description: String
)
What does this code do?
This code defines the building blocks of your vault's data structure.
- FileType is an enum with two values: IMAGE and TEXT. This lets you handle each file type differently in the UI later.
- VaultFile is a data class that holds a file's ID, which topic it belongs to, its display name, its type, and its content (either a drawable resource name for images or the actual text for text files).
- VaultTopic is a data class with an ID, name, and short description.
- Save VaultData.kt.
- Check the editor for red underlines. You should see none.
Seeing red underlines?
- Make sure the package com.example.knowledgevault line at the top matches your actual package name. You can check by looking at MainActivity.kt for its package declaration.
- Confirm you created the file inside the correct package folder, not in the root java directory.
Help me fix red underlines in my VaultData.kt file
Add the SampleData object with topics and files
Now you need actual data to populate the vault. You'll create a singleton object that holds hardcoded topics and files, plus helper functions to look up files by topic or by ID.
- In VaultData.kt, add the following code below the VaultTopic data class:
object SampleData {
// Three historical figures as vault topics
val topics = listOf(
VaultTopic("1", "John F. Kennedy", "35th President of the United States"),
VaultTopic("2", "Martin Luther King Jr.", "Civil rights leader and activist"),
VaultTopic("3", "Albert Einstein", "Theoretical physicist and Nobel laureate")
)
// Files associated with each topic (mix of images and text)
val files = listOf(
VaultFile("f1", "1", "JFK Portrait", FileType.IMAGE, "jfk_portrait"),
VaultFile("f2", "1", "Inaugural Address", FileType.TEXT, "And so, my fellow Americans: ask not what your country can do for you \u2014 ask what you can do for your country. My fellow citizens of the world: ask not what America will do for you, but what together we can do for the freedom of man."),
VaultFile("f3", "2", "MLK Portrait", FileType.IMAGE, "mlk_portrait"),
VaultFile("f4", "2", "I Have a Dream Excerpt", FileType.TEXT, "I have a dream that one day this nation will rise up and live out the true meaning of its creed: We hold these truths to be self-evident, that all men are created equal."),
VaultFile("f5", "2", "Letter from Birmingham Jail Excerpt", FileType.TEXT, "Injustice anywhere is a threat to justice everywhere. We are caught in an inescapable network of mutuality, tied in a single garment of destiny."),
VaultFile("f6", "3", "Einstein Portrait", FileType.IMAGE, "einstein_portrait"),
VaultFile("f7", "3", "Theory of Relativity Notes", FileType.TEXT, "E = mc\u00B2 represents the equivalence of mass and energy. The special theory of relativity shows that the laws of physics are the same for all non-accelerating observers, and that the speed of light in a vacuum is the same regardless of the speed at which an observer travels.")
)
// Look up all files belonging to a specific topic
fun getFilesForTopic(topicId: String): List<VaultFile> {
return files.filter { it.topicId == topicId }
}
// Find a single file by its unique ID
fun getFileById(fileId: String): VaultFile? {
return files.find { it.id == fileId }
}
// Find a single topic by its unique ID
fun getTopicById(topicId: String): VaultTopic? {
return topics.find { it.id == topicId }
}
}
What does this code do?
The SampleData object acts as a simple in-memory database for your app. It holds three topics about famous historical figures, each with 2-3 associated files.
- IMAGE files store a drawable resource name (like jfk_portrait) in the content field. The app will use this name to load the image from your resources.
- TEXT files store the actual text content directly in the content field.
- The three helper functions (getFilesForTopic, getFileById, getTopicById) make it easy to query the data from your UI screens.
- Save VaultData.kt.
- Click Build > Make Project in the top menu (or press Cmd+F9 on macOS, Ctrl+F9 on Windows).
- You should see BUILD SUCCESSFUL in the Build output panel at the bottom of Android Studio.
Build failed?
- Check that you placed the SampleData object inside the same file, below the VaultTopic class but still within the same package.
- Make sure there are no stray curly braces or missing commas in the listOf() calls.
- Verify the FileType.IMAGE and FileType.TEXT references match the enum values defined above.
Help me fix my SampleData build errors
Add sample images to the drawable folder
Your IMAGE files reference drawable resource names like jfk_portrait. You need to add actual image files with these names to your project's resources so the app can display them later.
- Find three portrait images to use as samples. Search online for public domain portraits of JFK, Martin Luther King Jr., and Albert Einstein. Save them to your computer.
- In the Project window, expand app > res > drawable.
- Right-click the drawable folder.
- Select Open In > Finder (macOS) or Open In > Explorer (Windows) to open the folder on your computer.
- Copy your three downloaded images into this folder.
- Rename them to jfk_portrait.jpg, mlk_portrait.jpg, and einstein_portrait.jpg. Use .png if your images are in PNG format.
- Switch back to Android Studio. The files should appear in the drawable folder automatically.
Why do filenames matter here?
Android references drawable resources by their filename (without the extension). Your SampleData stores jfk_portrait as the content string for image files. At runtime, the app uses this string to look up the matching drawable resource. The filename must be lowercase with underscores only.
- Click Build > Make Project again to confirm everything compiles.
- You should see BUILD SUCCESSFUL with no errors.
Build error after adding images?
- Make sure image filenames contain only lowercase letters, numbers, and underscores. No spaces, hyphens, or capital letters.
- Confirm the images are in app/src/main/res/drawable/ and not a subdirectory.
- If Android Studio doesn't detect the new files, right-click the res folder and select Reload from Disk.
Help me fix drawable resource errors in my Android project
✔️ Awesome, I've got everything!
Great. Double check you've saved VaultData.kt and confirm the build is successful before moving on.
ⓧ I'd like to double check the full code
Here is the complete VaultData.kt file:
package com.example.knowledgevault
enum class FileType {
IMAGE,
TEXT
}
data class VaultFile(
val id: String,
val topicId: String,
val fileName: String,
val fileType: FileType,
val content: String
)
data class VaultTopic(
val id: String,
val name: String,
val description: String
)
object SampleData {
val topics = listOf(
VaultTopic("1", "John F. Kennedy", "35th President of the United States"),
VaultTopic("2", "Martin Luther King Jr.", "Civil rights leader and activist"),
VaultTopic("3", "Albert Einstein", "Theoretical physicist and Nobel laureate")
)
val files = listOf(
VaultFile("f1", "1", "JFK Portrait", FileType.IMAGE, "jfk_portrait"),
VaultFile("f2", "1", "Inaugural Address", FileType.TEXT, "And so, my fellow Americans: ask not what your country can do for you \u2014 ask what you can do for your country. My fellow citizens of the world: ask not what America will do for you, but what together we can do for the freedom of man."),
VaultFile("f3", "2", "MLK Portrait", FileType.IMAGE, "mlk_portrait"),
VaultFile("f4", "2", "I Have a Dream Excerpt", FileType.TEXT, "I have a dream that one day this nation will rise up and live out the true meaning of its creed: We hold these truths to be self-evident, that all men are created equal."),
VaultFile("f5", "2", "Letter from Birmingham Jail Excerpt", FileType.TEXT, "Injustice anywhere is a threat to justice everywhere. We are caught in an inescapable network of mutuality, tied in a single garment of destiny."),
VaultFile("f6", "3", "Einstein Portrait", FileType.IMAGE, "einstein_portrait"),
VaultFile("f7", "3", "Theory of Relativity Notes", FileType.TEXT, "E = mc\u00B2 represents the equivalence of mass and energy. The special theory of relativity shows that the laws of physics are the same for all non-accelerating observers, and that the speed of light in a vacuum is the same regardless of the speed at which an observer travels.")
)
fun getFilesForTopic(topicId: String): List<VaultFile> {
return files.filter { it.topicId == topicId }
}
fun getFileById(fileId: String): VaultFile? {
return files.find { it.id == fileId }
}
fun getTopicById(topicId: String): VaultTopic? {
return topics.find { it.id == topicId }
}
}
Your vault's data model is defined and compiling. Next up, you'll build the home screen that displays these topics and files in a scrollable list.
Build the Home Screen with Topics and Files
Your data model is ready with topics, files, and helper functions that connect them together. Now it's time to turn that structured data into something the user can actually see and interact with.
In this step, you'll build the home screen of the Knowledge Vault app. You'll use Jetpack Compose's LazyColumn to display a scrollable list of topic cards, each showing the topic name and its associated files underneath.
In this step, get ready to:
- Create a HomeScreen composable with a scrollable list of topics.
- Style each topic as a card with file rows that show type icons.
- Wire the HomeScreen into MainActivity so the app displays your vault data.
Create the HomeScreen composable
The home screen is the first thing a user sees when they open the app. It needs to display all topics in a vertically scrollable list, with each topic acting as a section header for its files.
- In the Android Studio Project pane on the left, right-click on the com.example.knowledgevault package (the same folder where VaultData.kt lives).
- Select New, then Kotlin Class/File.
- Type HomeScreen and select File from the list.
- Replace everything in the new file with the following code:
package com.example.knowledgevault
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Description
import androidx.compose.material.icons.filled.Image
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun HomeScreen(onFileClick: (String) -> Unit) {
// Scaffold provides the screen structure with a top app bar
Scaffold(
topBar = {
TopAppBar(
title = { Text("Knowledge Vault") },
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.surface,
titleContentColor = MaterialTheme.colorScheme.onSurface
)
)
}
) { paddingValues ->
// LazyColumn efficiently renders a scrollable vertical list
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
.padding(horizontal = 16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
item { Spacer(modifier = Modifier.height(8.dp)) }
items(SampleData.topics) { topic ->
TopicCard(topic = topic, onFileClick = onFileClick)
}
item { Spacer(modifier = Modifier.height(8.dp)) }
}
}
}
What does this code do?
Scaffold sets up the screen layout with a TopAppBar that displays the title "Knowledge Vault" at the top of the screen.
LazyColumn is Compose's efficient scrollable list. It only renders items currently visible on screen, which keeps performance smooth even with many topics. The items(SampleData.topics) call iterates through your sample data and creates a TopicCard for each one.
The onFileClick parameter is a callback function that will handle file taps later when you add navigation.
You won't see a change yet because TopicCard doesn't exist. You'll add it next.
Add the TopicCard and FileRow composables
Each topic needs a visual container that groups its name, description, and associated files together. You'll build this with two composables: TopicCard for the outer container and FileRow for each file item inside it.
- In HomeScreen.kt, add the TopicCard composable below the HomeScreen function by pasting this code at the bottom of the file:
@Composable
fun TopicCard(topic: VaultTopic, onFileClick: (String) -> Unit) {
// Get all files that belong to this topic
val topicFiles = SampleData.getFilesForTopic(topic.id)
Card(
modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surface
)
) {
Column(modifier = Modifier.padding(16.dp)) {
// Topic name as a styled header
Text(
text = topic.name,
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.primary
)
// Topic description in smaller text
Text(
text = topic.description,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f)
)
Spacer(modifier = Modifier.height(12.dp))
// Render each file as a tappable row
topicFiles.forEach { file ->
FileRow(file = file, onClick = { onFileClick(file.id) })
}
}
}
}
What does TopicCard do?
Card wraps each topic in a Material 3 card with a subtle background. Inside, a Column stacks the topic name, description, and file rows vertically.
The getFilesForTopic helper you wrote earlier filters the full files list down to only those belonging to this topic. Each file gets rendered as a FileRow (which you'll add next).
The file still won't compile because FileRow is referenced but doesn't exist yet. Add it now.
- Below the TopicCard function, paste the FileRow composable:
@Composable
fun FileRow(file: VaultFile, onClick: () -> Unit) {
Row(
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = onClick)
.padding(vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically
) {
// Show a different icon based on file type
Icon(
imageVector = when (file.fileType) {
FileType.IMAGE -> Icons.Filled.Image
FileType.TEXT -> Icons.Filled.Description
},
contentDescription = file.fileType.name,
modifier = Modifier.size(24.dp),
tint = MaterialTheme.colorScheme.primary
)
Spacer(modifier = Modifier.width(12.dp))
Column {
// File name as the primary label
Text(
text = file.fileName,
style = MaterialTheme.typography.bodyMedium
)
// File type shown below in lighter text
Text(
text = file.fileType.name,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f)
)
}
}
}
What does FileRow do?
Each FileRow displays a single file as a horizontal Row with an icon on the left and the file name on the right. The clickable modifier makes the entire row tappable, triggering the onClick callback.
The when expression picks the right icon based on file type: an image icon for FileType.IMAGE and a document icon for FileType.TEXT.
- Save HomeScreen.kt.
- Check that the file compiles without errors by looking at the bottom status bar in Android Studio. You should see no red error indicators in the file.
Seeing red underlines?
Make sure all three composables (HomeScreen, TopicCard, FileRow) are in the same file. If Android Studio highlights SampleData or VaultTopic in red, check that the package line at the top of HomeScreen.kt matches VaultData.kt exactly: com.example.knowledgevault.
help me fix red underlines in my HomeScreen.kt file
✔️ Awesome, I've got everything!
Great. Double check you've saved your file before moving on.
ⓧ I'd like to double check the full code
package com.example.knowledgevault
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Description
import androidx.compose.material.icons.filled.Image
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun HomeScreen(onFileClick: (String) -> Unit) {
Scaffold(
topBar = {
TopAppBar(
title = { Text("Knowledge Vault") },
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.surface,
titleContentColor = MaterialTheme.colorScheme.onSurface
)
)
}
) { paddingValues ->
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
.padding(horizontal = 16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
item { Spacer(modifier = Modifier.height(8.dp)) }
items(SampleData.topics) { topic ->
TopicCard(topic = topic, onFileClick = onFileClick)
}
item { Spacer(modifier = Modifier.height(8.dp)) }
}
}
}
@Composable
fun TopicCard(topic: VaultTopic, onFileClick: (String) -> Unit) {
val topicFiles = SampleData.getFilesForTopic(topic.id)
Card(
modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surface
)
) {
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = topic.name,
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.primary
)
Text(
text = topic.description,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f)
)
Spacer(modifier = Modifier.height(12.dp))
topicFiles.forEach { file ->
FileRow(file = file, onClick = { onFileClick(file.id) })
}
}
}
}
@Composable
fun FileRow(file: VaultFile, onClick: () -> Unit) {
Row(
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = onClick)
.padding(vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = when (file.fileType) {
FileType.IMAGE -> Icons.Filled.Image
FileType.TEXT -> Icons.Filled.Description
},
contentDescription = file.fileType.name,
modifier = Modifier.size(24.dp),
tint = MaterialTheme.colorScheme.primary
)
Spacer(modifier = Modifier.width(12.dp))
Column {
Text(
text = file.fileName,
style = MaterialTheme.typography.bodyMedium
)
Text(
text = file.fileType.name,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f)
)
}
}
}
Wire the HomeScreen into MainActivity
The HomeScreen composable exists, but the app still shows the default greeting because MainActivity hasn't been updated to use it.
- Open MainActivity.kt from the Project pane (it's in the same com.example.knowledgevault package).
- Replace the entire contents of the file with the following code:
package com.example.knowledgevault
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Surface
import androidx.compose.material3.MaterialTheme
import androidx.compose.ui.Modifier
import com.example.knowledgevault.ui.theme.KnowledgeVaultTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
KnowledgeVaultTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
// Display the home screen with an empty click handler for now
HomeScreen(onFileClick = { })
}
}
}
}
}
What changed in MainActivity?
You removed the default Greeting composable and replaced it with your HomeScreen. The KnowledgeVaultTheme wrapper applies your app's Material 3 theme.
The onFileClick = { } passes an empty lambda because navigation doesn't exist yet. You'll replace this with real navigation logic in the next step.
- Save MainActivity.kt.
- Run the app on your emulator or physical device by clicking the green play button in the toolbar.
- You should see a scrollable list of three topic cards (John F. Kennedy, Martin Luther King Jr., Albert Einstein) with their files listed underneath each one.
- Try tapping a file row. Nothing happens yet, which is expected.
Don't see the topic list?
If the app still shows "Hello Android" or the default greeting, make sure you saved both HomeScreen.kt and MainActivity.kt. Also confirm that MainActivity.kt calls HomeScreen(onFileClick = { }) inside setContent.
If you see a build error mentioning an unresolved reference to KnowledgeVaultTheme, check that the import line com.example.knowledgevault.ui.theme.KnowledgeVaultTheme matches your project's generated theme package.
help me troubleshoot why my Knowledge Vault home screen is not showing
Your home screen is alive with data. Next up, you'll build a detail screen and add navigation so tapping any file actually opens its content.
Build the Detail Screen and Add Navigation
Your home screen displays topics and files in a scrollable list, but nothing happens when you tap on them. For this to feel like a real app, you need a way to transition between screens based on what the user taps.
In this step, you'll use Navigation Compose to create a two-screen app. The home screen will navigate to a detail screen that renders the content of each file, whether it's an image or text.
In this step, get ready to:
- Set up a NavHost with routes for the home and detail screens.
- Create a DetailScreen composable that renders images and text.
- Connect file taps to navigate between screens.
Set up navigation routes in MainActivity
Navigation in Compose works through a NavHost. This is a container that swaps composables in and out based on the current route. Each route is a string that identifies a screen.
- In Android Studio, open MainActivity.kt from the Project panel on the left.
- Replace the entire file content with the following code:
package com.example.knowledgevault
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Surface
import androidx.compose.material3.MaterialTheme
import androidx.compose.ui.Modifier
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.example.knowledgevault.ui.theme.KnowledgeVaultTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
KnowledgeVaultTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
// Create a navigation controller to manage screen transitions
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = "home"
) {
// Home screen route
composable("home") {
HomeScreen(
onFileClick = { fileId ->
navController.navigate("detail/$fileId")
}
)
}
// Detail screen route with fileId parameter
composable("detail/{fileId}") { backStackEntry ->
val fileId = backStackEntry.arguments?.getString("fileId") ?: ""
DetailScreen(
fileId = fileId,
onBackClick = { navController.popBackStack() }
)
}
}
}
}
}
}
}
What does this code do?
rememberNavController() creates a navigation controller that tracks which screen is currently displayed and manages the back stack.
NavHost defines the navigation graph. It takes two routes: "home" for the topic list, and "detail/{fileId}" for viewing a specific file. The curly braces in the route are a path parameter, similar to URLs on the web.
When a file is tapped, navController.navigate("detail/$fileId") switches to the detail screen. popBackStack() navigates back to the previous screen.
You'll see a red error on DetailScreen because that composable doesn't exist yet. You'll create it next.
✔️ Awesome, I've got everything!
Great. Make sure you've saved the file before moving on.
ⓧ I'd like to double check the full code
package com.example.knowledgevault
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Surface
import androidx.compose.material3.MaterialTheme
import androidx.compose.ui.Modifier
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.example.knowledgevault.ui.theme.KnowledgeVaultTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
KnowledgeVaultTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = "home"
) {
composable("home") {
HomeScreen(
onFileClick = { fileId ->
navController.navigate("detail/$fileId")
}
)
}
composable("detail/{fileId}") { backStackEntry ->
val fileId = backStackEntry.arguments?.getString("fileId") ?: ""
DetailScreen(
fileId = fileId,
onBackClick = { navController.popBackStack() }
)
}
}
}
}
}
}
}
Create the detail screen composable
The detail screen's job is to display the actual content of a file. For images, it shows the picture full-width. For text files, it displays the text content in a scrollable layout.
- Right-click the com.example.knowledgevault package in the Project panel.
- Select New, then Kotlin Class/File.
- Choose File and name it DetailScreen.
- Replace the file content with the following code:
package com.example.knowledgevault
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DetailScreen(fileId: String, onBackClick: () -> Unit) {
// Look up the file from sample data using the passed ID
val file = SampleData.getFileById(fileId)
Scaffold(
topBar = {
TopAppBar(
title = { Text(file?.fileName ?: "File Not Found") },
navigationIcon = {
// Back arrow button to return to home
IconButton(onClick = onBackClick) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = "Back"
)
}
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.surface,
titleContentColor = MaterialTheme.colorScheme.onSurface
)
)
}
) { paddingValues ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
.padding(16.dp)
.verticalScroll(rememberScrollState())
) {
if (file == null) {
Text("File not found", style = MaterialTheme.typography.bodyLarge)
} else {
// Render different content based on file type
when (file.fileType) {
FileType.IMAGE -> {
val context = LocalContext.current
val resourceId = context.resources.getIdentifier(
file.content, "drawable", context.packageName
)
if (resourceId != 0) {
Image(
painter = painterResource(id = resourceId),
contentDescription = file.fileName,
modifier = Modifier.fillMaxWidth(),
contentScale = ContentScale.FillWidth
)
} else {
Text(
text = "Image not found: ${file.content}",
style = MaterialTheme.typography.bodyLarge
)
}
}
FileType.TEXT -> {
Text(
text = file.fileName,
style = MaterialTheme.typography.headlineSmall,
color = MaterialTheme.colorScheme.primary
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = file.content,
style = MaterialTheme.typography.bodyLarge,
lineHeight = MaterialTheme.typography.bodyLarge.lineHeight
)
}
}
}
}
}
}
What does this code do?
- SampleData.getFileById(fileId) looks up the file matching the ID passed through navigation. If no match is found, it returns null.
- The TopAppBar shows the file name as the title and a back arrow (Icons.AutoMirrored.Filled.ArrowBack) that calls onBackClick when tapped.
- The when block checks the file type. For IMAGE files, it resolves the drawable resource by name and displays it. For TEXT files, it shows the file name as a heading and the content below.
- The fallback message "Image not found" appears when a drawable resource name doesn't match any image in your project. This prevents crashes from missing images.
- Save DetailScreen.kt.
- Check that the red error on DetailScreen in MainActivity.kt has disappeared. The project should now compile without errors.
Still seeing a red error on DetailScreen?
- Make sure the file is named DetailScreen.kt and is inside the com.example.knowledgevault package. If it's in a different package, the import won't resolve.
- Check that the package declaration at the top of DetailScreen.kt says package com.example.knowledgevault.
- Try clicking File then Sync Project with Gradle Files to refresh the project index.
Help me fix my DetailScreen import error
✔️ Awesome, I've got everything!
Great. Make sure you've saved the file before moving on.
ⓧ I'd like to double check the full code
package com.example.knowledgevault
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DetailScreen(fileId: String, onBackClick: () -> Unit) {
val file = SampleData.getFileById(fileId)
Scaffold(
topBar = {
TopAppBar(
title = { Text(file?.fileName ?: "File Not Found") },
navigationIcon = {
IconButton(onClick = onBackClick) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = "Back"
)
}
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.surface,
titleContentColor = MaterialTheme.colorScheme.onSurface
)
)
}
) { paddingValues ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
.padding(16.dp)
.verticalScroll(rememberScrollState())
) {
if (file == null) {
Text("File not found", style = MaterialTheme.typography.bodyLarge)
} else {
when (file.fileType) {
FileType.IMAGE -> {
val context = LocalContext.current
val resourceId = context.resources.getIdentifier(
file.content, "drawable", context.packageName
)
if (resourceId != 0) {
Image(
painter = painterResource(id = resourceId),
contentDescription = file.fileName,
modifier = Modifier.fillMaxWidth(),
contentScale = ContentScale.FillWidth
)
} else {
Text(
text = "Image not found: ${file.content}",
style = MaterialTheme.typography.bodyLarge
)
}
}
FileType.TEXT -> {
Text(
text = file.fileName,
style = MaterialTheme.typography.headlineSmall,
color = MaterialTheme.colorScheme.primary
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = file.content,
style = MaterialTheme.typography.bodyLarge,
lineHeight = MaterialTheme.typography.bodyLarge.lineHeight
)
}
}
}
}
}
}
Test the navigation flow
Both screens are in place and the NavHost connects them. The flow works like this: when you tap a file row in HomeScreen, the onFileClick callback fires, which tells the NavController to navigate to the detail route with that file's ID.
- Run the app on your emulator or physical device by clicking the green play button in the toolbar.
- Tap on a text file item, such as Inaugural Address under John F. Kennedy.
- You should see the detail screen with the file name as a heading and the full text content below it.
- Tap the back arrow in the top-left corner to return to the home screen.
- Tap on an image file item, such as JFK Portrait. You should see the message Image not found: jfk_portrait because the drawable images haven't been added to the project yet.
Why do image files show a fallback message?
The image file entries in your sample data reference drawable resource names like jfk_portrait. These image files don't exist in your res/drawable folder yet.
The detail screen handles this gracefully by checking if the resource ID is valid before trying to load the image. You'll add actual images in the next step when you polish the UI.
App crashes when you tap a file?
- Check that the route string in MainActivity.kt uses "detail/{fileId}" with curly braces. Without the braces, Navigation won't recognize it as a parameter.
- Make sure DetailScreen accepts fileId: String as its first parameter. A type mismatch here causes a compile error that may appear as a runtime crash.
- Look at the Logcat panel in Android Studio for the full error message. Filter by your app's package name to find the relevant crash log.
Help me debug my navigation crash
Your app now has full navigation between two screens. Next up, you'll polish the UI with a custom color theme, add file type icons, and deploy the finished app to your physical phone.
Polish the UI and Deploy to Your Phone
Your app is fully functional with working navigation between the home screen and detail screen. Now it is time to make it look intentional.
A custom color theme transforms a default-looking app into one that feels designed. In this step, you will apply a dark vault-inspired palette, confirm the existing icon and ripple effects pick up the new colors, and deploy the finished app to your physical phone.
In this step, get ready to:
- Apply a custom dark color theme with vault-inspired accent colors.
- Verify type-specific icons and tap feedback use the new theme.
- Build and install the app on your physical Android phone.
Apply a custom vault color theme
Android Studio's Empty Activity template generates a default theme with purple and pink tones. You will replace those defaults with a dark color palette that gives your vault app a distinct identity.
- In the Project panel on the left side of Android Studio, expand app/src/main/java/com/example/knowledgevault/ui/theme/.
- Open Color.kt.
- Select all existing content and replace it with the following vault color palette:
package com.example.knowledgevault.ui.theme
import androidx.compose.ui.graphics.Color
// Dark background colors for the vault feel
val VaultDark = Color(0xFF1A1A2E)
val VaultMedium = Color(0xFF16213E)
// Accent colors for interactive elements
val VaultAccent = Color(0xFF0F3460)
val VaultHighlight = Color(0xFFE94560)
// Text colors for readability on dark backgrounds
val VaultText = Color(0xFFEAEAEA)
val VaultTextSecondary = Color(0xFFB0B0B0)
What do these colors do?
Each color serves a specific role in the dark theme. VaultDark and VaultMedium are deep navy tones for backgrounds and surfaces.
VaultHighlight is a vibrant red-pink that draws attention to interactive elements like topic names and icons. VaultText is a near-white for readable body text on dark backgrounds.
- Save Color.kt.
- Now open Theme.kt in the same ui/theme/ folder.
- Select all existing content and replace it with this dark color scheme:
package com.example.knowledgevault.ui.theme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.runtime.Composable
// Map vault colors to Material 3 color roles
private val DarkColorScheme = darkColorScheme(
primary = VaultHighlight,
secondary = VaultAccent,
background = VaultDark,
surface = VaultMedium,
onPrimary = VaultText,
onSecondary = VaultText,
onBackground = VaultText,
onSurface = VaultText
)
@Composable
fun KnowledgeVaultTheme(content: @Composable () -> Unit) {
MaterialTheme(
colorScheme = DarkColorScheme,
content = content
)
}
How does the theme connect to your screens?
Your MainActivity already wraps everything in KnowledgeVaultTheme. Every composable that references MaterialTheme.colorScheme automatically picks up the new vault colors without any additional wiring.
For example, MaterialTheme.colorScheme.primary now resolves to VaultHighlight instead of the default purple. Your topic names and file icons will appear in that vibrant red-pink color.
- Save Theme.kt.
- Run the app on your emulator by clicking the green Run button (or press Ctrl+R on macOS, Shift+F10 on Windows).
- You should see a dark navy background with red-pink topic names and file icons.
Still seeing the default purple theme?
Make sure you saved both Color.kt and Theme.kt. Android Studio shows a dot on the tab name for unsaved files.
Check that the package declaration at the top of both files is com.example.knowledgevault.ui.theme. A mismatch means MainActivity cannot find your updated theme.
help me fix my vault theme colors not showing up
Verify icons and ripple effects with the new theme
Your HomeScreen already uses Icons.Filled.Image for IMAGE files and Icons.Filled.Description for TEXT files. The clickable modifier in Jetpack Compose automatically adds a Material 3 ripple animation on tap.
- On the emulator, look at the file rows under each topic card.
- Confirm that IMAGE files show a photo icon and TEXT files show a document icon, both in the red-pink VaultHighlight color.
- Tap any file row and watch for the ripple animation spreading from your tap point.
Why do ripple effects work without extra code?
In Material 3, the clickable modifier includes a ripple indication by default. The ripple color is derived from your theme's colorScheme, so switching to the vault theme automatically updates the ripple appearance.
Build and install on your physical phone
Running on a real device lets you feel the touch interactions and see the colors on actual hardware. You need to enable Developer Options on your phone first.
- On your Android phone, open Settings.
- Scroll down and tap About Phone (on some devices this is under System first).
- Find Build Number and tap it seven times until you see a message confirming Developer Mode is enabled.
- Go back to the main Settings screen and open the new Developer Options menu.
- Toggle on USB Debugging.
- Connect your phone to your computer with a USB cable.
- When prompted on your phone, tap Allow to authorize USB debugging from this computer.
What is USB debugging?
USB debugging allows Android Studio to communicate with your phone over USB. It lets Android Studio install and run apps directly on the device without going through the Play Store.
- In Android Studio, click the device dropdown near the Run button at the top of the window.
- Select your physical phone from the list (it appears by its model name).
- Click the green Run button to build and install the app on your phone.
- Once the app launches on your phone, scroll through the topic list to verify all three topics appear.
- Tap each file to open its detail screen.
- Use the back arrow to return to the home screen after each file.
- Confirm the dark vault theme, red-pink icons, and ripple feedback all display correctly on the physical screen.
Phone not showing up in the device list?
Make sure USB Debugging is toggled on in Developer Options. Some phones require you to select a USB mode of File Transfer (MTP) instead of Charging only.
Try disconnecting and reconnecting the USB cable. Watch for the authorization prompt on your phone and tap Allow.
help me connect my Android phone to Android Studio for USB debugging
✔️ Awesome, I've got everything!
Great. Double check you have saved both Color.kt and Theme.kt and the app runs with the dark vault theme on your phone.
ⓧ I'd like to double check the full code
Here is the complete code for the two files you updated in this step.
package com.example.knowledgevault.ui.theme
import androidx.compose.ui.graphics.Color
val VaultDark = Color(0xFF1A1A2E)
val VaultMedium = Color(0xFF16213E)
val VaultAccent = Color(0xFF0F3460)
val VaultHighlight = Color(0xFFE94560)
val VaultText = Color(0xFFEAEAEA)
val VaultTextSecondary = Color(0xFFB0B0B0)
package com.example.knowledgevault.ui.theme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.runtime.Composable
private val DarkColorScheme = darkColorScheme(
primary = VaultHighlight,
secondary = VaultAccent,
background = VaultDark,
surface = VaultMedium,
onPrimary = VaultText,
onSecondary = VaultText,
onBackground = VaultText,
onSurface = VaultText
)
@Composable
fun KnowledgeVaultTheme(content: @Composable () -> Unit) {
MaterialTheme(
colorScheme = DarkColorScheme,
content = content
)
}
Your vault app is polished and running on real hardware. Next up, you will tackle a bonus challenge to add category filtering with state management.
Secret mission
Add a Category Filter
Your vault mixes images and text files together in every topic. What if you want to browse only images, or only text? Add a row of filter chips to the home screen so you can view files by type.
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely locally, so there are no ongoing costs. Decide whether to keep your resources as they are, or remove them from your device and computer.
Resources you used:
- The KnowledgeVault app installed on your Android phone or emulator.
- The KnowledgeVault project folder on your computer.
Keep everything running
No action needed. Choose this if you plan to continue building on your Knowledge Vault app in future projects.
- The app stays installed on your phone or emulator and is ready to use anytime.
- Your project files remain in Android Studio so you can pick up right where you left off.
Pause - I'll come back to this later
Close Android Studio to free up memory on your computer. Your project files and installed app remain intact for when you return.
- Close Android Studio. Your project saves automatically.
- If you are using an emulator, close it to free up RAM.
- The app stays on your phone or emulator until you manually uninstall it.
Delete - I don't want to use this again
Remove all project resources from your device and computer.
- Uninstall the app from your Android phone or emulator by long-pressing the app icon on your home screen and selecting Uninstall.
- Delete the project folder from your computer. On macOS or Linux, run:
rm -rf ~/AndroidStudioProjects/KnowledgeVault
On Windows, run:
rmdir /s /q %USERPROFILE%\AndroidStudioProjects\KnowledgeVault
- In Android Studio, if the project still appears in your recent projects list, right-click it and select Remove from Recent Projects.
Nice Work!
Nice Work!
Nice work! You just built a fully functional Knowledge Vault app for Android from scratch using Kotlin and Jetpack Compose.
You've learned how to:
- Built a native Android app from scratch using Kotlin and Jetpack Compose, including a custom dark color theme with Material 3 components.
- Created a scrollable topic list with LazyColumn and a structured data model using data classes to represent topics and files.
- Implemented multi-screen navigation with tap-to-open functionality that renders images and text content in a detail view, and deployed the app to a physical Android device.
- Secret Mission: Added category filtering with state management using FilterChip composables to show only specific file types.
Ready to quiz yourself?