Bookmark this page. Use Ctrl+F (or Cmd+F on Mac) to find what you need. This cheat sheet covers Jetpack Compose components, modifiers, state, navigation, and common patterns.

Last updated: April 2026

Core Composables

ComposableUsage
Text("Hello")Display text
Text("Bold", fontWeight = FontWeight.Bold)Styled text
Button(onClick = { }) { Text("Click") }Clickable button
OutlinedButton(onClick = { }) { Text("Outlined") }Outlined variant
TextButton(onClick = { }) { Text("Text") }Text-only button
IconButton(onClick = { }) { Icon(...) }Icon button
TextField(value, onValueChange = { })Text input field
OutlinedTextField(value, onValueChange = { })Outlined text input
Image(painter, contentDescription)Display an image
Icon(Icons.Default.Home, contentDescription)Material icon
Checkbox(checked, onCheckedChange)Checkbox
Switch(checked, onCheckedChange)Toggle switch
RadioButton(selected, onClick)Radio button
Slider(value, onValueChange)Slider
CircularProgressIndicator()Loading spinner
LinearProgressIndicator(progress)Progress bar
HorizontalDivider()Horizontal divider line (replaces deprecated Divider())
Spacer(modifier = Modifier.height(16.dp))Empty space

Layouts

// Column — vertical stack
Column {
    Text("First")
    Text("Second")
}

// Row — horizontal stack
Row {
    Text("Left")
    Spacer(Modifier.weight(1f))
    Text("Right")
}

// Box — stack on top of each other
Box {
    Image(...)           // background
    Text("Overlay")      // on top
}

// LazyColumn — scrollable vertical list (RecyclerView replacement)
LazyColumn {
    items(list) { item ->
        Text(item.name)
    }
}

// LazyRow — scrollable horizontal list
LazyRow {
    items(list) { item ->
        Card { Text(item.name) }
    }
}

// LazyVerticalGrid — grid layout
LazyVerticalGrid(columns = GridCells.Fixed(2)) {
    items(list) { item ->
        Card { Text(item.name) }
    }
}

Common Modifiers

Modifiers change the appearance and behavior of composables. Order matters — modifiers are applied top to bottom.

ModifierEffect
.padding(16.dp)Add padding inside
.padding(horizontal = 16.dp, vertical = 8.dp)Directional padding
.fillMaxWidth()Fill parent width
.fillMaxSize()Fill parent width + height
.size(48.dp)Fixed width and height
.width(200.dp) / .height(100.dp)Fixed one dimension
.weight(1f)Fill remaining space (in Row/Column)
.background(Color.Red)Background color
.background(Color.Red, RoundedCornerShape(8.dp))Rounded background
.clip(RoundedCornerShape(12.dp))Clip to shape
.clip(CircleShape)Clip to circle
.border(1.dp, Color.Gray, RoundedCornerShape(8.dp))Add border
.clickable { }Make clickable
.shadow(4.dp, RoundedCornerShape(8.dp))Drop shadow
.alpha(0.5f)Transparency
.offset(x = 10.dp, y = 5.dp)Move position
.align(Alignment.CenterHorizontally)Align in Column
.verticalScroll(rememberScrollState())Make scrollable

Modifier Order Matters

// Padding THEN background — padding is outside the color
Box(Modifier.padding(16.dp).background(Color.Red))

// Background THEN padding — padding is inside the color
Box(Modifier.background(Color.Red).padding(16.dp))

State

// remember — survives recomposition
var count by remember { mutableStateOf(0) }

// rememberSaveable — survives configuration changes (rotation)
var name by rememberSaveable { mutableStateOf("") }

// derivedStateOf — computed state that only updates when dependencies change
val isValid by remember {
    derivedStateOf { name.isNotBlank() && name.length >= 3 }
}

// Collecting Flow as State
val uiState by viewModel.uiState.collectAsStateWithLifecycle()

State Hoisting Pattern

// Stateless composable — state is "hoisted" to the caller
@Composable
fun Counter(count: Int, onIncrement: () -> Unit) {
    Button(onClick = onIncrement) {
        Text("Count: $count")
    }
}

// Stateful parent
@Composable
fun CounterScreen() {
    var count by remember { mutableStateOf(0) }
    Counter(count = count, onIncrement = { count++ })
}

Type-safe navigation (recommended since 2024) uses Kotlin Serialization instead of string routes:

// Define routes as data classes
@Serializable data object Home
@Serializable data class Detail(val id: String)

// Setup
val navController = rememberNavController()
NavHost(navController, startDestination = Home) {
    composable<Home> { HomeScreen(navController) }
    composable<Detail> { backStackEntry ->
        val detail = backStackEntry.toRoute<Detail>()
        DetailScreen(detail.id)
    }
}

// Navigate
navController.navigate(Detail(id = "42"))

// Navigate and clear back stack
navController.navigate(Home) {
    popUpTo<Home> { inclusive = true }
}

// Go back
navController.popBackStack()

Material 3 Components

// Scaffold — app structure with top bar, bottom bar, FAB
Scaffold(
    topBar = { TopAppBar(title = { Text("My App") }) },
    bottomBar = { NavigationBar { /* items */ } },
    floatingActionButton = {
        FloatingActionButton(onClick = { }) { Icon(Icons.Default.Add, null) }
    }
) { padding ->
    // Content goes here
    Column(Modifier.padding(padding)) { }
}

// Card
Card(
    modifier = Modifier.fillMaxWidth().padding(8.dp),
    elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
) {
    Text("Card content", Modifier.padding(16.dp))
}

// AlertDialog
AlertDialog(
    onDismissRequest = { showDialog = false },
    title = { Text("Confirm") },
    text = { Text("Are you sure?") },
    confirmButton = { TextButton(onClick = { }) { Text("Yes") } },
    dismissButton = { TextButton(onClick = { }) { Text("No") } }
)

// BottomSheet
ModalBottomSheet(onDismissRequest = { }) {
    Column(Modifier.padding(16.dp)) {
        Text("Bottom sheet content")
    }
}

Theming

// Access current theme values anywhere
MaterialTheme.colorScheme.primary
MaterialTheme.colorScheme.surface
MaterialTheme.colorScheme.onSurface
MaterialTheme.typography.headlineMedium
MaterialTheme.typography.bodyLarge
MaterialTheme.shapes.medium

Side Effects

EffectWhen to use
LaunchedEffect(key) { }Run suspend function when key changes
DisposableEffect(key) { onDispose { } }Setup + cleanup (like listeners)
rememberCoroutineScope()Get scope for launching coroutines from callbacks
SideEffect { }Run after every successful recomposition
rememberUpdatedState(value)Capture latest value in a long-running effect
// Load data when screen appears
LaunchedEffect(Unit) {
    viewModel.loadData()
}

// Cleanup a listener
DisposableEffect(lifecycleOwner) {
    val observer = LifecycleEventObserver { _, event -> }
    lifecycleOwner.lifecycle.addObserver(observer)
    onDispose { lifecycleOwner.lifecycle.removeObserver(observer) }
}

// Launch coroutine from a click handler
val scope = rememberCoroutineScope()
Button(onClick = { scope.launch { doWork() } }) {
    Text("Start")
}

Animations

// AnimatedVisibility — show/hide with animation
AnimatedVisibility(visible = isVisible) {
    Text("I fade in and out")
}

// animateContentSize — smooth size changes
Column(Modifier.animateContentSize()) {
    Text("Title")
    if (expanded) {
        Text("Extra content that animates in")
    }
}

// Animate a value
val alpha by animateFloatAsState(if (visible) 1f else 0f)
Box(Modifier.alpha(alpha)) { Text("Fading") }

// Crossfade between composables
Crossfade(targetState = currentTab) { tab ->
    when (tab) {
        Tab.Home -> HomeScreen()
        Tab.Profile -> ProfileScreen()
    }
}

Flow Layouts

// FlowRow — wraps items to next line when full
FlowRow(
    horizontalArrangement = Arrangement.spacedBy(8.dp),
    verticalArrangement = Arrangement.spacedBy(8.dp)
) {
    tags.forEach { tag ->
        AssistChip(onClick = { }, label = { Text(tag) })
    }
}

// FlowColumn — vertical wrapping
FlowColumn(
    verticalArrangement = Arrangement.spacedBy(8.dp)
) {
    items.forEach { Text(it) }
}

Common Patterns

Loading / Error / Success

when (val state = uiState) {
    is UiState.Loading -> CircularProgressIndicator()
    is UiState.Error -> Text("Error: ${state.message}")
    is UiState.Success -> LazyColumn {
        items(state.data) { item -> ItemCard(item) }
    }
}

Search Field with Debounce

var query by remember { mutableStateOf("") }
LaunchedEffect(query) {
    delay(300) // debounce 300ms
    viewModel.search(query)
}
OutlinedTextField(value = query, onValueChange = { query = it })

Pull-to-Refresh

// Material 3 pull-to-refresh
val pullState = rememberPullToRefreshState()
PullToRefreshBox(
    isRefreshing = isRefreshing,
    onRefresh = { viewModel.refresh() },
    state = pullState
) {
    LazyColumn { /* content */ }
}

Note: Navigation3 (NavDisplay, scenes) became stable in March 2026 as a Compose-first alternative to NavController/NavHost. For new projects, consider evaluating Navigation3 alongside the type-safe Navigation Compose approach shown above.

Common Mistakes

  1. Forgetting remember — Without remember, state resets on every recomposition. var count = mutableStateOf(0) resets to 0 every time the composable re-executes. Always use var count by remember { mutableStateOf(0) }.

  2. Wrong modifier orderModifier.padding(16.dp).background(Color.Red) gives padding outside the red area. Modifier.background(Color.Red).padding(16.dp) gives padding inside. Think of modifiers as wrapping layers from outside to inside.

  3. Recomposition traps — Avoid creating new objects inside composables without remember. For example, val list = listOf(1, 2, 3) creates a new list on every recomposition. Use val list = remember { listOf(1, 2, 3) } for stable references.