Skip to content

Jetpack Compose

UI & GraphicsUI FrameworkKotlin

What it is

Jetpack Compose is Android's declarative UI toolkit, describing interfaces as composable functions of state rather than as mutable view hierarchies.

A @Composable function emits UI from its parameters. When state it reads changes, Compose recomposes just that function rather than mutating a view tree.

Installation

implementation(platform("androidx.compose:compose-bom:2024.09.00"))

Getting started

The smallest useful thing you can do with it, and what each part means.

State hoisting
@Composable
fun BookList(
    books: List<Book>,
    selectedId: Int?,
    onSelect: (Int) -> Unit,     // state comes in, events go out
) {
    LazyColumn {
        items(books, key = { it.id }) { book ->
            ListItem(
                headlineContent = { Text(book.title) },
                supportingContent = { Text(book.author) },
                modifier = Modifier
                    .clickable { onSelect(book.id) }
                    .background(if (book.id == selectedId)
                        MaterialTheme.colorScheme.secondaryContainer
                        else Color.Transparent),
            )
        }
    }
}
The `key` in items is important: without it, Compose reuses items by position, so removing an entry can leave the wrong state attached to a row. Hoisting state makes this composable trivially testable and previewable.

Advanced usage

Where the library earns its place over a simpler alternative.

Effects and collecting state safely
@Composable
fun BookScreen(viewModel: BookViewModel = hiltViewModel()) {
    // Stops collecting when the screen is not visible — a plain
    // collectAsState keeps working in the background and wastes battery.
    val state by viewModel.state.collectAsStateWithLifecycle()

    // Runs when id changes, cancels the previous run automatically.
    LaunchedEffect(viewModel.id) { viewModel.load() }

    // Survives recomposition rather than being recreated each time.
    val listState = rememberLazyListState()

    when (val s = state) {
        is UiState.Loading -> CircularProgressIndicator()
        is UiState.Error   -> ErrorMessage(s.message, onRetry = viewModel::load)
        is UiState.Ready   -> BookList(s.books, s.selectedId, viewModel::select)
    }
}
collectAsStateWithLifecycle rather than collectAsState is the difference between a screen that stops working when backgrounded and one that keeps a network subscription alive off-screen.

Errors and fixes

The failures you are most likely to hit, and what actually resolves them.

The UI recomposes constantly
An unstable parameter — often a lambda or list recreated each pass. Use remember, and prefer immutable types.
Work restarts on every recomposition
A side effect ran in the composable body. Move it into LaunchedEffect with the right keys.

Best practices

  • Hoist state: composables should take values and emit events, not own mutable state.
  • Use collectAsStateWithLifecycle on Android, not collectAsState.
  • Provide a stable key in items so list state survives insertions and removals.
  • Never perform I/O directly in a composable; use LaunchedEffect or the ViewModel.

Background

Why it exists, and what it was reacting to.

Compose replaced twelve years of XML layouts and findViewById. Google now treats it as the recommended way to build Android UI, and Compose Multiplatform extends it to desktop, iOS and web.