Back to Blog

Lists & Navigation in Kotlin Desktop: Compose Multiplatform | Lesson 04

Sandy LaneSandy Lane

Video: Lists & Navigation in Kotlin Desktop: Compose Multiplatform | Lesson 04 by Taught by Celeste AI - AI Coding Coach

Watch full page →

Lists & Navigation in Kotlin Desktop: Compose Multiplatform | Lesson 04

In this lesson, we create a notes app using Compose Multiplatform for Kotlin Desktop, focusing on building efficient scrollable lists with LazyColumn and implementing type-safe navigation between screens. We use sealed classes to represent different screens and a when expression to route UI based on the current screen state, demonstrating state hoisting for navigation control.

Code

import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

// Define screens as a sealed class for type-safe navigation
sealed class Screen {
  object List : Screen()
  data class Detail(val noteId: Int) : Screen()
}

@Composable
fun NotesApp() {
  var currentScreen by remember { mutableStateOf(Screen.List) }

  Scaffold(
    topBar = {
      TopBar(
        title = when (currentScreen) {
          is Screen.List -> "Notes"
          is Screen.Detail -> "Note Details"
        },
        showBackButton = currentScreen is Screen.Detail,
        onBack = { currentScreen = Screen.List }
      )
    }
  ) { padding ->
    // Screen routing based on currentScreen state
    when (val screen = currentScreen) {
      is Screen.List -> NotesList(
        notes = List(20) { "Note #$it" },
        onNoteClick = { id -> currentScreen = Screen.Detail(id) },
        modifier = Modifier.padding(padding)
      )
      is Screen.Detail -> NoteDetail(
        noteId = screen.noteId,
        modifier = Modifier
          .padding(padding)
          .verticalScroll(rememberScrollState())
      )
    }
  }
}

@Composable
fun TopBar(title: String, showBackButton: Boolean, onBack: () -> Unit) {
  SmallTopAppBar(
    title = { Text(title) },
    navigationIcon = if (showBackButton) {
      {
        IconButton(onClick = onBack) {
          Icon(Icons.Default.ArrowBack, contentDescription = "Back")
        }
      }
    } else null
  )
}

@Composable
fun NotesList(notes: List, onNoteClick: (Int) -> Unit, modifier: Modifier = Modifier) {
  LazyColumn(modifier = modifier.fillMaxSize()) {
    items(notes.indices) { index ->
      Card(
        modifier = Modifier
          .fillMaxWidth()
          .padding(8.dp)
          .clickable { onNoteClick(index) },
        elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
      ) {
        Text(
          text = notes[index],
          modifier = Modifier.padding(16.dp)
        )
      }
    }
  }
}

@Composable
fun NoteDetail(noteId: Int, modifier: Modifier = Modifier) {
  Column(modifier = modifier.fillMaxSize().padding(16.dp)) {
    Text("Details for Note #$noteId", style = MaterialTheme.typography.headlineMedium)
    Spacer(Modifier.height(8.dp))
    Text("Here you can show the full content of the note with id $noteId.")
  }
}

Key Points

  • LazyColumn with items() efficiently renders large scrollable lists in Compose Desktop.
  • Clickable list items are created using Card with the clickable modifier to handle user interaction.
  • Sealed classes model app screens, enabling type-safe and clear navigation states.
  • A when expression drives screen routing based on the current navigation state.
  • State hoisting keeps navigation state in a parent composable, promoting unidirectional data flow and easy state management.