0
# Collections and Observables
1
2
Reactive collections and observable data structures that integrate with the Compose runtime system. These collections automatically trigger recomposition when their contents change.
3
4
## Capabilities
5
6
### State Lists
7
8
Mutable lists that trigger recomposition when modified.
9
10
```kotlin { .api }
11
/**
12
* Creates a mutable list that triggers recomposition when changed
13
* @param elements Initial elements for the list
14
* @return SnapshotStateList instance
15
*/
16
fun <T> mutableStateListOf(vararg elements: T): SnapshotStateList<T>
17
18
/**
19
* Mutable list implementation that integrates with Compose state system
20
* Modifications to this list will trigger recomposition of observing composables
21
*/
22
interface SnapshotStateList<T> : MutableList<T> {
23
/**
24
* Adds all elements from the specified collection
25
* @param elements Collection of elements to add
26
* @return true if list was modified
27
*/
28
fun addAll(elements: Collection<T>): Boolean
29
30
/**
31
* Removes all elements that match the predicate
32
* @param predicate Function to test elements
33
* @return true if any elements were removed
34
*/
35
fun removeAll(predicate: (T) -> Boolean): Boolean
36
}
37
```
38
39
**Usage Examples:**
40
41
```kotlin
42
import androidx.compose.runtime.*
43
44
@Composable
45
fun StateListExample() {
46
val items = remember { mutableStateListOf("Apple", "Banana", "Cherry") }
47
48
Column {
49
// Display list items - recomposes when list changes
50
items.forEach { item ->
51
Text("• $item")
52
}
53
54
Row {
55
Button(onClick = { items.add("Orange") }) {
56
Text("Add Orange")
57
}
58
59
Button(onClick = { items.removeAt(0) }) {
60
Text("Remove First")
61
}
62
}
63
}
64
}
65
66
@Composable
67
fun TodoListExample() {
68
val todos = remember { mutableStateListOf<Todo>() }
69
var newTodoText by remember { mutableStateOf("") }
70
71
Column {
72
LazyColumn {
73
items(todos) { todo ->
74
TodoItem(
75
todo = todo,
76
onToggle = {
77
val index = todos.indexOf(todo)
78
todos[index] = todo.copy(completed = !todo.completed)
79
},
80
onDelete = { todos.remove(todo) }
81
)
82
}
83
}
84
85
Row {
86
TextField(
87
value = newTodoText,
88
onValueChange = { newTodoText = it }
89
)
90
Button(
91
onClick = {
92
if (newTodoText.isNotEmpty()) {
93
todos.add(Todo(text = newTodoText, completed = false))
94
newTodoText = ""
95
}
96
}
97
) {
98
Text("Add")
99
}
100
}
101
}
102
}
103
```
104
105
### State Maps
106
107
Mutable maps that trigger recomposition when modified.
108
109
```kotlin { .api }
110
/**
111
* Creates a mutable map that triggers recomposition when changed
112
* @param pairs Initial key-value pairs for the map
113
* @return SnapshotStateMap instance
114
*/
115
fun <K, V> mutableStateMapOf(vararg pairs: Pair<K, V>): SnapshotStateMap<K, V>
116
117
/**
118
* Mutable map implementation that integrates with Compose state system
119
* Modifications to this map will trigger recomposition of observing composables
120
*/
121
interface SnapshotStateMap<K, V> : MutableMap<K, V> {
122
/**
123
* Puts all key-value pairs from the specified map
124
* @param from Map containing pairs to add
125
*/
126
fun putAll(from: Map<out K, V>)
127
128
/**
129
* Removes entries that match the predicate
130
* @param predicate Function to test entries
131
* @return true if any entries were removed
132
*/
133
fun removeAll(predicate: (Map.Entry<K, V>) -> Boolean): Boolean
134
}
135
```
136
137
**Usage Examples:**
138
139
```kotlin
140
@Composable
141
fun StateMapExample() {
142
val userScores = remember {
143
mutableStateMapOf(
144
"Alice" to 100,
145
"Bob" to 85,
146
"Charlie" to 92
147
)
148
}
149
150
Column {
151
Text("Leaderboard")
152
153
// Display scores - recomposes when map changes
154
userScores.entries.sortedByDescending { it.value }.forEach { (name, score) ->
155
Text("$name: $score points")
156
}
157
158
Row {
159
Button(onClick = { userScores["Alice"] = (userScores["Alice"] ?: 0) + 10 }) {
160
Text("Boost Alice")
161
}
162
163
Button(onClick = { userScores["David"] = 75 }) {
164
Text("Add David")
165
}
166
}
167
168
Text("Total players: ${userScores.size}")
169
}
170
}
171
172
@Composable
173
fun ConfigurationExample() {
174
val settings = remember {
175
mutableStateMapOf(
176
"darkMode" to false,
177
"notifications" to true,
178
"autoSave" to true
179
)
180
}
181
182
Column {
183
settings.entries.forEach { (key, value) ->
184
Row {
185
Switch(
186
checked = value as Boolean,
187
onCheckedChange = { settings[key] = it }
188
)
189
Text(key)
190
}
191
}
192
193
Button(
194
onClick = {
195
settings.clear()
196
// Reset to defaults
197
settings.putAll(mapOf(
198
"darkMode" to false,
199
"notifications" to true,
200
"autoSave" to true
201
))
202
}
203
) {
204
Text("Reset Settings")
205
}
206
}
207
}
208
```
209
210
### Flow Integration
211
212
Convert reactive streams to Compose State.
213
214
```kotlin { .api }
215
/**
216
* Collects values from a Flow and represents them as State
217
* @param initial Initial value to use before first emission
218
* @param context CoroutineContext for collection (default: EmptyCoroutineContext)
219
* @return State that updates with Flow emissions
220
*/
221
@Composable
222
fun <T> Flow<T>.collectAsState(
223
initial: T,
224
context: CoroutineContext = EmptyCoroutineContext
225
): State<T>
226
227
/**
228
* Collects values from a StateFlow and represents them as State
229
* Uses the StateFlow's current value as initial value
230
* @param context CoroutineContext for collection (default: EmptyCoroutineContext)
231
* @return State that updates with StateFlow emissions
232
*/
233
@Composable
234
fun <T> StateFlow<T>.collectAsState(
235
context: CoroutineContext = EmptyCoroutineContext
236
): State<T>
237
```
238
239
**Usage Examples:**
240
241
```kotlin
242
@Composable
243
fun FlowCollectionExample(repository: DataRepository) {
244
// Collect list data from Flow
245
val items by repository.getItemsFlow().collectAsState(
246
initial = emptyList()
247
)
248
249
// Collect search results
250
val searchQuery by repository.searchQueryFlow().collectAsState(
251
initial = ""
252
)
253
254
val filteredItems = remember(items, searchQuery) {
255
if (searchQuery.isEmpty()) items else items.filter {
256
it.name.contains(searchQuery, ignoreCase = true)
257
}
258
}
259
260
LazyColumn {
261
items(filteredItems) { item ->
262
ItemRow(item = item)
263
}
264
}
265
}
266
267
@Composable
268
fun ViewModelExample(viewModel: MyViewModel) {
269
// Collect UI state from ViewModel
270
val uiState by viewModel.uiState.collectAsState()
271
272
// Collect loading state
273
val isLoading by viewModel.isLoading.collectAsState()
274
275
when (uiState) {
276
is UiState.Loading -> LoadingIndicator()
277
is UiState.Success -> {
278
SuccessContent(
279
data = uiState.data,
280
isRefreshing = isLoading
281
)
282
}
283
is UiState.Error -> ErrorMessage(uiState.message)
284
}
285
}
286
```
287
288
### List Extensions
289
290
Useful extensions for working with state lists.
291
292
```kotlin { .api }
293
/**
294
* Converts a regular List to a SnapshotStateList
295
*/
296
fun <T> List<T>.toMutableStateList(): SnapshotStateList<T>
297
298
/**
299
* Creates a state list from a collection
300
*/
301
fun <T> Collection<T>.toMutableStateList(): SnapshotStateList<T>
302
```
303
304
**Usage Examples:**
305
306
```kotlin
307
@Composable
308
fun ListConversionExample() {
309
// Convert existing list to state list
310
val originalList = listOf("A", "B", "C")
311
val stateList = remember { originalList.toMutableStateList() }
312
313
// Now mutations will trigger recomposition
314
Button(onClick = { stateList.add("D") }) {
315
Text("Add D")
316
}
317
}
318
319
@Composable
320
fun DataLoadingExample(initialData: List<String>) {
321
val items = remember(initialData) {
322
initialData.toMutableStateList()
323
}
324
325
LaunchedEffect(Unit) {
326
val newItems = loadAdditionalData()
327
items.addAll(newItems)
328
}
329
330
LazyColumn {
331
items(items) { item ->
332
Text(item)
333
}
334
}
335
}
336
```
337
338
### Snapshot System Integration
339
340
Advanced usage of the snapshot system underlying state collections.
341
342
```kotlin { .api }
343
/**
344
* Takes a snapshot of the current state
345
* @return Snapshot instance
346
*/
347
fun Snapshot.Companion.take(): Snapshot
348
349
/**
350
* Creates a mutable snapshot for making isolated changes
351
*/
352
fun Snapshot.Companion.takeMutableSnapshot(
353
readObserver: ((Any) -> Unit)? = null,
354
writeObserver: ((Any) -> Unit)? = null
355
): MutableSnapshot
356
357
/**
358
* Base class for snapshots of the state system
359
*/
360
abstract class Snapshot {
361
companion object
362
363
/**
364
* Applies changes made in this snapshot
365
* @return SnapshotApplyResult indicating success or conflicts
366
*/
367
abstract fun apply(): SnapshotApplyResult
368
369
/**
370
* Disposes of this snapshot, releasing resources
371
*/
372
abstract fun dispose()
373
}
374
375
/**
376
* A mutable snapshot that allows making changes within an isolated scope
377
*/
378
abstract class MutableSnapshot : Snapshot {
379
/**
380
* Enters the snapshot scope for making changes
381
*/
382
abstract fun <T> enter(block: () -> T): T
383
}
384
385
/**
386
* Result of applying a snapshot
387
*/
388
sealed class SnapshotApplyResult {
389
object Success : SnapshotApplyResult()
390
data class Failure(val snapshot: Snapshot) : SnapshotApplyResult()
391
}
392
```
393
394
**Usage Examples:**
395
396
```kotlin
397
@Composable
398
fun SnapshotExample() {
399
val items = remember { mutableStateListOf("A", "B", "C") }
400
401
Button(
402
onClick = {
403
// Make changes in an isolated snapshot
404
val snapshot = Snapshot.takeMutableSnapshot()
405
snapshot.enter {
406
items.add("D")
407
items.add("E")
408
// Changes aren't visible yet
409
}
410
411
// Apply changes atomically
412
snapshot.apply()
413
snapshot.dispose()
414
}
415
) {
416
Text("Add Multiple Items")
417
}
418
}
419
```
420
421
## Collection Patterns
422
423
### Filtered Collections
424
425
Create derived collections that update automatically.
426
427
```kotlin
428
@Composable
429
fun FilteredCollectionExample() {
430
val allItems = remember { mutableStateListOf<Item>() }
431
var filterText by remember { mutableStateOf("") }
432
433
val filteredItems = remember(allItems, filterText) {
434
derivedStateOf {
435
if (filterText.isEmpty()) {
436
allItems.toList()
437
} else {
438
allItems.filter { it.name.contains(filterText, ignoreCase = true) }
439
}
440
}
441
}.value
442
443
Column {
444
TextField(
445
value = filterText,
446
onValueChange = { filterText = it },
447
label = { Text("Filter") }
448
)
449
450
LazyColumn {
451
items(filteredItems) { item ->
452
ItemCard(item = item)
453
}
454
}
455
}
456
}
457
```
458
459
### Grouped Collections
460
461
Group collections by key with automatic updates.
462
463
```kotlin
464
@Composable
465
fun GroupedCollectionExample() {
466
val items = remember { mutableStateListOf<Task>() }
467
468
val groupedItems = remember(items) {
469
derivedStateOf {
470
items.groupBy { it.category }
471
}
472
}.value
473
474
LazyColumn {
475
groupedItems.forEach { (category, tasks) ->
476
item {
477
Text(
478
text = category,
479
style = MaterialTheme.typography.h6
480
)
481
}
482
483
items(tasks) { task ->
484
TaskItem(task = task)
485
}
486
}
487
}
488
}
489
```
490
491
### Sorted Collections
492
493
Maintain sorted collections that update automatically.
494
495
```kotlin
496
@Composable
497
fun SortedCollectionExample() {
498
val items = remember { mutableStateListOf<Product>() }
499
var sortBy by remember { mutableStateOf(SortCriteria.NAME) }
500
501
val sortedItems = remember(items, sortBy) {
502
derivedStateOf {
503
when (sortBy) {
504
SortCriteria.NAME -> items.sortedBy { it.name }
505
SortCriteria.PRICE -> items.sortedBy { it.price }
506
SortCriteria.RATING -> items.sortedByDescending { it.rating }
507
}
508
}
509
}.value
510
511
Column {
512
SortingOptions(
513
currentSort = sortBy,
514
onSortChange = { sortBy = it }
515
)
516
517
LazyColumn {
518
items(sortedItems) { product ->
519
ProductCard(product = product)
520
}
521
}
522
}
523
}
524
```