What You Will Build

A multi-selection interface where users can select multiple items from a list of cards with checkboxes. Selected items are highlighted with a primary container color, and a counter shows how many items are selected. This is the standard filter/preference selection pattern.

Why This Pattern Matters

Multi-selection is used in filter screens, preference pages, team assignment dialogs, and tag selection. Managing a Set of selected items with immutable state updates is a fundamental Compose pattern that applies to any selection-based UI.

Step 1: Selection State

val options = listOf(
    "Design", "Development", "Marketing", "Sales",
    "Support", "Management", "Research", "Analytics"
)
var selected by remember { mutableStateOf(setOf<String>()) }

Step 2: Build the Selection List

@Composable
fun MultiSelectionScreen() {
    Column(modifier = Modifier.fillMaxSize().padding(16.dp)) {
        Text("Select Departments",
            style = MaterialTheme.typography.headlineSmall,
            fontWeight = FontWeight.Bold)
        Spacer(Modifier.height(8.dp))
        Text("Selected: ${selected.size} of ${options.size}",
            color = MaterialTheme.colorScheme.onSurfaceVariant)
        Spacer(Modifier.height(16.dp))

        options.forEach { option ->
            val isSelected = option in selected
            Card(
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(vertical = 4.dp)
                    .clickable {
                        selected = if (isSelected) selected - option
                                   else selected + option
                    },
                colors = CardDefaults.cardColors(
                    containerColor = if (isSelected)
                        MaterialTheme.colorScheme.primaryContainer
                    else MaterialTheme.colorScheme.surface
                )
            ) {
                Row(
                    Modifier.padding(16.dp),
                    verticalAlignment = Alignment.CenterVertically
                ) {
                    Checkbox(
                        checked = isSelected,
                        onCheckedChange = {
                            selected = if (isSelected) selected - option
                                       else selected + option
                        }
                    )
                    Spacer(Modifier.width(12.dp))
                    Text(option,
                        style = MaterialTheme.typography.bodyLarge)
                }
            }
        }
    }
}

Key Pattern: Set-Based Selection

Using a Set<String> for selection state provides O(1) lookup with the in operator, automatic deduplication, and clean add/remove with + and - operators:

// Add to selection
selected = selected + option  // Set union

// Remove from selection
selected = selected - option  // Set difference

// Check membership
val isSelected = option in selected  // O(1) lookup

Tips and Pitfalls

  • Use Set, not List, for selection state. Set provides O(1) contains checks and prevents accidental duplicates.
  • Both Card.clickable and Checkbox.onCheckedChange should perform the same toggle action so the entire card is tappable.
  • primaryContainer color on selected cards provides visual feedback beyond just the checkbox state.
  • For large lists: Move options into a LazyColumn and add search filtering above the list.

Min SDK: 21 | Compose BOM: 2024.01.00+

Get New Tutorials by Email

No spam. Just clear, practical breakdowns you can apply right away.

Enjoy this tutorial?

Get new practical tech tutorials in your inbox.