Phase 2 step-by-step
Build a multi-screen business catalogue.
Outcome: a junior-level app with list, detail, favourites, settings, search, filters, and call/map/share actions.
Add navigation dependency
Open app/build.gradle.kts, add Navigation Compose, sync Gradle, and run the app once.
dependencies {
implementation("androidx.navigation:navigation-compose:2.9.8")
}Gradle sync succeeds. If Android Studio suggests a newer stable version, use that.
Create the catalogue model and sample data
Add a data class and five local records. Keep data local first so navigation and UI are easy to debug.
data class Business(
val id: Int,
val name: String,
val category: String,
val city: String,
val phone: String,
val address: String
)
val sampleBusinesses = listOf(
Business(1, "Asha Clinic", "Clinic", "Shivamogga", "9876543210", "MG Road"),
Business(2, "Future Steps School", "School", "Davanagere", "9876500001", "PB Road"),
Business(3, "RankPlus Coaching", "Coaching", "Mysuru", "9876500002", "VV Mohalla")
)Build the list screen first
Render the list without navigation. This proves the data model and row UI work.
@Composable
fun BusinessListScreen(
businesses: List<Business>,
onOpen: (Int) -> Unit
) {
LazyColumn(verticalArrangement = Arrangement.spacedBy(8.dp)) {
items(businesses, key = { it.id }) { business ->
Card(modifier = Modifier.fillMaxWidth().clickable { onOpen(business.id) }) {
Column(modifier = Modifier.padding(12.dp)) {
Text(business.name, fontWeight = FontWeight.Bold)
Text("${business.category} • ${business.city}")
}
}
}
}
}Checkpoint: all sample businesses render in a scrollable list.
Add NavHost and detail route
Move from one screen to two screens. Pass only the id through the route.
@Composable
fun CatalogueApp() {
val navController = rememberNavController()
NavHost(navController, startDestination = "list") {
composable("list") {
BusinessListScreen(sampleBusinesses) { id ->
navController.navigate("detail/$id")
}
}
composable("detail/{id}") { entry ->
val id = entry.arguments?.getString("id")?.toIntOrNull()
val business = sampleBusinesses.firstOrNull { it.id == id }
BusinessDetailScreen(business, onBack = { navController.popBackStack() })
}
}
}Tapping a row opens detail; Back returns to the list.
Add search and category filters
Keep filtering as a pure function so it can be tested later.
fun filterBusinesses(items: List<Business>, query: String, category: String?): List<Business> {
val clean = query.trim().lowercase()
return items.filter {
val queryMatch = clean.isBlank() ||
it.name.lowercase().contains(clean) ||
it.city.lowercase().contains(clean)
val categoryMatch = category == null || it.category == category
queryMatch && categoryMatch
}
}Checkpoint: searching “mys” shows Mysuru rows; selecting Clinic hides non-clinic rows.
Add favourites
Use a set of ids. Persist it later with Room; first make the flow correct.
var favourites by remember { mutableStateOf(setOf<Int>()) }
fun toggleFavourite(id: Int) {
favourites = if (id in favourites) favourites - id else favourites + id
}Rows can be saved and the favourites screen shows only saved businesses.
Add device actions
Use external intents for actions Android already supports.
fun openDialer(context: Context, phone: String) {
context.startActivity(Intent(Intent.ACTION_DIAL, Uri.parse("tel:$phone")))
}
fun openMap(context: Context, address: String) {
val uri = Uri.parse("geo:0,0?q=${Uri.encode(address)}")
context.startActivity(Intent(Intent.ACTION_VIEW, uri))
}Checkpoint: detail screen has Call, Map, and Share buttons. Missing apps on emulator are documented as known limitations.
Add app states
Do not show a blank area. A junior app needs visible success, empty, and error states.
sealed interface CatalogueState {
data object Loading : CatalogueState
data class Success(val businesses: List<Business>) : CatalogueState
data object Empty : CatalogueState
data class Error(val message: String) : CatalogueState
}Common failures
Navigation symbols unresolvedSync Gradle after adding navigation-compose and import navigation.compose APIs.
Back button exits appUse navController.popBackStack() on detail back action.
Favourites resetExpected for this step. Phase 3 introduces persistence and repository ownership.
Map does not openThe emulator may not have a map app. Test real device or handle ActivityNotFoundException later.