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+