0
# State Management
1
2
State handling and lifecycle management for reactive UI updates and side effects in Compose applications, enabling dynamic and interactive user interfaces.
3
4
## Capabilities
5
6
### Core State APIs
7
8
Fundamental state management functions for creating and managing reactive state in Compose applications.
9
10
```kotlin { .api }
11
/**
12
* Remember a value across recompositions.
13
*/
14
@Composable
15
fun <T> remember(calculation: () -> T): T
16
17
/**
18
* Remember a value with a single key for cache invalidation.
19
*/
20
@Composable
21
fun <T> remember(
22
key1: Any?,
23
calculation: () -> T
24
): T
25
26
/**
27
* Remember a value with two keys for cache invalidation.
28
*/
29
@Composable
30
fun <T> remember(
31
key1: Any?,
32
key2: Any?,
33
calculation: () -> T
34
): T
35
36
/**
37
* Remember a value with three keys for cache invalidation.
38
*/
39
@Composable
40
fun <T> remember(
41
key1: Any?,
42
key2: Any?,
43
key3: Any?,
44
calculation: () -> T
45
): T
46
47
/**
48
* Remember a value with arbitrary keys for cache invalidation.
49
*/
50
@Composable
51
fun <T> remember(
52
vararg keys: Any?,
53
calculation: () -> T
54
): T
55
56
/**
57
* Create a mutable state holder with structural equality policy.
58
*/
59
fun <T> mutableStateOf(
60
value: T,
61
policy: SnapshotMutationPolicy<T> = structuralEqualityPolicy()
62
): MutableState<T>
63
64
/**
65
* Create a mutable state holder with referential equality policy.
66
*/
67
fun <T> mutableStateOf(
68
value: T,
69
policy: SnapshotMutationPolicy<T> = referentialEqualityPolicy()
70
): MutableState<T>
71
72
/**
73
* A state holder that represents a value and allows observation of changes.
74
*/
75
interface State<out T> {
76
val value: T
77
}
78
79
/**
80
* A mutable state holder that can be read and written.
81
*/
82
interface MutableState<T> : State<T> {
83
override var value: T
84
operator fun component1(): T
85
operator fun component2(): (T) -> Unit
86
}
87
88
/**
89
* Create a derived state that is calculated from other state values.
90
*/
91
@Composable
92
fun <T> derivedStateOf(calculation: () -> T): State<T>
93
94
/**
95
* Mutation policies for state management.
96
*/
97
fun <T> structuralEqualityPolicy(): SnapshotMutationPolicy<T>
98
fun <T> referentialEqualityPolicy(): SnapshotMutationPolicy<T>
99
fun <T> neverEqualPolicy(): SnapshotMutationPolicy<T>
100
101
/**
102
* Policy for determining when state changes trigger recomposition.
103
*/
104
interface SnapshotMutationPolicy<T> {
105
fun equivalent(a: T, b: T): Boolean
106
fun merge(previous: T, current: T, applied: T): T?
107
}
108
```
109
110
**Usage Examples:**
111
112
```kotlin
113
// Basic state management
114
@Composable
115
fun StateExample() {
116
// Simple state with remember and mutableStateOf
117
var count by remember { mutableStateOf(0) }
118
var text by remember { mutableStateOf("") }
119
120
Column(
121
modifier = Modifier.padding(16.dp),
122
verticalArrangement = Arrangement.spacedBy(16.dp)
123
) {
124
Text("Count: $count")
125
126
Row {
127
Button(onClick = { count++ }) {
128
Text("Increment")
129
}
130
Button(onClick = { count-- }) {
131
Text("Decrement")
132
}
133
Button(onClick = { count = 0 }) {
134
Text("Reset")
135
}
136
}
137
138
TextField(
139
value = text,
140
onValueChange = { text = it },
141
label = { Text("Enter text") }
142
)
143
144
Text("Text length: ${text.length}")
145
}
146
}
147
148
// Derived state example
149
@Composable
150
fun DerivedStateExample() {
151
var firstName by remember { mutableStateOf("") }
152
var lastName by remember { mutableStateOf("") }
153
154
// Derived state that recalculates when firstName or lastName changes
155
val fullName by derivedStateOf {
156
"$firstName $lastName".trim()
157
}
158
159
Column(modifier = Modifier.padding(16.dp)) {
160
TextField(
161
value = firstName,
162
onValueChange = { firstName = it },
163
label = { Text("First Name") }
164
)
165
166
TextField(
167
value = lastName,
168
onValueChange = { lastName = it },
169
label = { Text("Last Name") }
170
)
171
172
Text("Full Name: $fullName")
173
Text("Name is ${if (fullName.isNotEmpty()) "not " else ""}empty")
174
}
175
}
176
177
// State with keys for cache invalidation
178
@Composable
179
fun KeyedStateExample(userId: String) {
180
// State that resets when userId changes
181
var userPreferences by remember(userId) {
182
mutableStateOf(loadUserPreferences(userId))
183
}
184
185
// Expensive calculation that only recalculates when specific values change
186
val processedData by remember(userPreferences.theme, userPreferences.language) {
187
derivedStateOf {
188
processUserData(userPreferences.theme, userPreferences.language)
189
}
190
}
191
192
// UI using the state...
193
}
194
195
fun loadUserPreferences(userId: String): UserPreferences = TODO()
196
fun processUserData(theme: String, language: String): ProcessedData = TODO()
197
```
198
199
### Side Effects
200
201
System for performing side effects and managing lifecycle in Compose applications.
202
203
```kotlin { .api }
204
/**
205
* Launch a coroutine tied to the composition lifecycle.
206
*/
207
@Composable
208
fun LaunchedEffect(
209
key1: Any?,
210
block: suspend CoroutineScope.() -> Unit
211
)
212
213
/**
214
* Launch a coroutine with multiple keys.
215
*/
216
@Composable
217
fun LaunchedEffect(
218
key1: Any?,
219
key2: Any?,
220
block: suspend CoroutineScope.() -> Unit
221
)
222
223
/**
224
* Launch a coroutine with arbitrary keys.
225
*/
226
@Composable
227
fun LaunchedEffect(
228
vararg keys: Any?,
229
block: suspend CoroutineScope.() -> Unit
230
)
231
232
/**
233
* Create and remember a coroutine scope tied to the composition.
234
*/
235
@Composable
236
fun rememberCoroutineScope(): CoroutineScope
237
238
/**
239
* Create a disposable effect that cleans up when keys change or composition exits.
240
*/
241
@Composable
242
fun DisposableEffect(
243
key1: Any?,
244
effect: DisposableEffectScope.() -> DisposableEffectResult
245
)
246
247
/**
248
* Create a disposable effect with multiple keys.
249
*/
250
@Composable
251
fun DisposableEffect(
252
key1: Any?,
253
key2: Any?,
254
effect: DisposableEffectScope.() -> DisposableEffectResult
255
)
256
257
/**
258
* Create a disposable effect with arbitrary keys.
259
*/
260
@Composable
261
fun DisposableEffect(
262
vararg keys: Any?,
263
effect: DisposableEffectScope.() -> DisposableEffectResult
264
)
265
266
/**
267
* Scope for disposable effects.
268
*/
269
interface DisposableEffectScope {
270
/**
271
* Create a disposal callback.
272
*/
273
fun onDispose(onDisposeEffect: () -> Unit): DisposableEffectResult
274
}
275
276
/**
277
* Result of a disposable effect.
278
*/
279
interface DisposableEffectResult
280
281
/**
282
* Create a side effect that runs after every recomposition.
283
*/
284
@Composable
285
fun SideEffect(effect: () -> Unit)
286
```
287
288
**Usage Examples:**
289
290
```kotlin
291
// LaunchedEffect for one-time or key-dependent side effects
292
@Composable
293
fun LaunchedEffectExample(userId: String) {
294
var userData by remember { mutableStateOf<UserData?>(null) }
295
var isLoading by remember { mutableStateOf(false) }
296
var error by remember { mutableStateOf<String?>(null) }
297
298
// Launch effect when userId changes
299
LaunchedEffect(userId) {
300
isLoading = true
301
error = null
302
try {
303
userData = fetchUserData(userId)
304
} catch (e: Exception) {
305
error = e.message
306
} finally {
307
isLoading = false
308
}
309
}
310
311
// One-time effect (runs only once)
312
LaunchedEffect(Unit) {
313
// Initialize analytics, start location updates, etc.
314
initializeApp()
315
}
316
317
when {
318
isLoading -> Text("Loading...")
319
error != null -> Text("Error: $error", color = Color.Red)
320
userData != null -> UserDataDisplay(userData!!)
321
else -> Text("No data")
322
}
323
}
324
325
// DisposableEffect for cleanup
326
@Composable
327
fun DisposableEffectExample() {
328
val lifecycleOwner = LocalLifecycleOwner.current
329
330
DisposableEffect(lifecycleOwner) {
331
val observer = LifecycleEventObserver { _, event ->
332
when (event) {
333
Lifecycle.Event.ON_START -> {
334
// Start location updates, register listeners, etc.
335
}
336
Lifecycle.Event.ON_STOP -> {
337
// Stop location updates, unregister listeners, etc.
338
}
339
else -> {}
340
}
341
}
342
343
lifecycleOwner.lifecycle.addObserver(observer)
344
345
onDispose {
346
lifecycleOwner.lifecycle.removeObserver(observer)
347
}
348
}
349
}
350
351
// RememberCoroutineScope for event-driven coroutines
352
@Composable
353
fun CoroutineScopeExample() {
354
val scope = rememberCoroutineScope()
355
var result by remember { mutableStateOf("") }
356
357
Column {
358
Button(onClick = {
359
scope.launch {
360
result = "Loading..."
361
try {
362
delay(2000) // Simulate network call
363
result = "Data loaded successfully!"
364
} catch (e: Exception) {
365
result = "Error: ${e.message}"
366
}
367
}
368
}) {
369
Text("Load Data")
370
}
371
372
Text(result)
373
}
374
}
375
376
// SideEffect for non-compose state synchronization
377
@Composable
378
fun SideEffectExample(value: String) {
379
// Update external system whenever value changes
380
SideEffect {
381
// This runs after every recomposition where value might have changed
382
updateExternalSystem(value)
383
}
384
}
385
386
suspend fun fetchUserData(userId: String): UserData = TODO()
387
fun initializeApp() = TODO()
388
fun updateExternalSystem(value: String) = TODO()
389
```
390
391
### State Persistence
392
393
System for persisting state across configuration changes and app restarts.
394
395
```kotlin { .api }
396
/**
397
* Remember a value that survives configuration changes.
398
*/
399
@Composable
400
fun <T : Any> rememberSaveable(
401
vararg inputs: Any?,
402
saver: Saver<T, out Any> = autoSaver(),
403
key: String? = null,
404
init: () -> T
405
): T
406
407
/**
408
* Remember a mutable state that survives configuration changes.
409
*/
410
@Composable
411
fun <T : Any> rememberSaveable(
412
vararg inputs: Any?,
413
stateSaver: Saver<T, out Any> = autoSaver(),
414
key: String? = null,
415
init: () -> MutableState<T>
416
): MutableState<T>
417
418
/**
419
* Interface for saving and restoring state.
420
*/
421
interface Saver<Original, Saveable : Any> {
422
fun save(value: Original): Saveable?
423
fun restore(value: Saveable): Original?
424
}
425
426
/**
427
* Create a Saver using save and restore functions.
428
*/
429
fun <Original, Saveable : Any> Saver(
430
save: (value: Original) -> Saveable?,
431
restore: (value: Saveable) -> Original?
432
): Saver<Original, Saveable>
433
434
/**
435
* Create a list saver for collections.
436
*/
437
fun <Original, Saveable : Any> listSaver(
438
save: (value: Original) -> List<Saveable>,
439
restore: (list: List<Saveable>) -> Original?
440
): Saver<Original, Any>
441
442
/**
443
* Create a map saver for complex objects.
444
*/
445
fun <T : Any> mapSaver(
446
save: (value: T) -> Map<String, Any?>,
447
restore: (map: Map<String, Any?>) -> T?
448
): Saver<T, Any>
449
450
/**
451
* Automatic saver that works with basic types.
452
*/
453
fun <T> autoSaver(): Saver<T, Any>
454
```
455
456
**Usage Examples:**
457
458
```kotlin
459
// Basic saveable state
460
@Composable
461
fun SaveableStateExample() {
462
// This state survives configuration changes
463
var count by rememberSaveable { mutableStateOf(0) }
464
var text by rememberSaveable { mutableStateOf("") }
465
466
Column {
467
Text("Count: $count (survives rotation)")
468
Button(onClick = { count++ }) {
469
Text("Increment")
470
}
471
472
TextField(
473
value = text,
474
onValueChange = { text = it },
475
label = { Text("Persistent text") }
476
)
477
}
478
}
479
480
// Custom saver for complex objects
481
data class UserProfile(
482
val name: String,
483
val email: String,
484
val age: Int
485
)
486
487
val UserProfileSaver = Saver<UserProfile, Map<String, Any>>(
488
save = { profile ->
489
mapOf(
490
"name" to profile.name,
491
"email" to profile.email,
492
"age" to profile.age
493
)
494
},
495
restore = { map ->
496
UserProfile(
497
name = map["name"] as String,
498
email = map["email"] as String,
499
age = map["age"] as Int
500
)
501
}
502
)
503
504
@Composable
505
fun CustomSaverExample() {
506
var userProfile by rememberSaveable(saver = UserProfileSaver) {
507
mutableStateOf(UserProfile("", "", 0))
508
}
509
510
Column {
511
TextField(
512
value = userProfile.name,
513
onValueChange = { userProfile = userProfile.copy(name = it) },
514
label = { Text("Name") }
515
)
516
517
TextField(
518
value = userProfile.email,
519
onValueChange = { userProfile = userProfile.copy(email = it) },
520
label = { Text("Email") }
521
)
522
523
// Age input field...
524
}
525
}
526
527
// List saver example
528
val IntListSaver = listSaver<List<Int>, Int>(
529
save = { it },
530
restore = { it }
531
)
532
533
@Composable
534
fun ListSaverExample() {
535
var numbers by rememberSaveable(saver = IntListSaver) {
536
mutableStateOf(listOf<Int>())
537
}
538
539
Column {
540
Button(onClick = {
541
numbers = numbers + (numbers.size + 1)
542
}) {
543
Text("Add Number")
544
}
545
546
LazyColumn {
547
items(numbers) { number ->
548
Text("Number: $number")
549
}
550
}
551
}
552
}
553
```
554
555
### State Hoisting
556
557
Patterns for managing state in larger component hierarchies and sharing state between components.
558
559
```kotlin { .api }
560
/**
561
* Snapshot state management for batch updates.
562
*/
563
object Snapshot {
564
/**
565
* Take a snapshot of the current state.
566
*/
567
fun takeSnapshot(readObserver: ((Any) -> Unit)? = null): Snapshot
568
569
/**
570
* Send and apply all changes in a snapshot.
571
*/
572
fun sendApplyNotifications()
573
574
/**
575
* Create a mutable snapshot for making changes.
576
*/
577
fun takeMutableSnapshot(
578
readObserver: ((Any) -> Unit)? = null,
579
writeObserver: ((Any) -> Unit)? = null
580
): MutableSnapshot
581
582
/**
583
* Observe state reads within a block.
584
*/
585
fun <T> observe(
586
readObserver: ((Any) -> Unit)?,
587
writeObserver: ((Any) -> Unit)? = null,
588
block: () -> T
589
): T
590
591
/**
592
* Run a block without triggering state reads.
593
*/
594
fun <T> withoutReadObservation(block: () -> T): T
595
596
/**
597
* Run a block with all mutations applied immediately.
598
*/
599
fun <T> withMutableSnapshot(block: MutableSnapshot.() -> T): T
600
}
601
602
/**
603
* Create a snapshot state list.
604
*/
605
fun <T> mutableStateListOf(vararg elements: T): SnapshotStateList<T>
606
607
/**
608
* Create a snapshot state map.
609
*/
610
fun <K, V> mutableStateMapOf(vararg pairs: Pair<K, V>): SnapshotStateMap<K, V>
611
612
/**
613
* A list that participates in snapshot state management.
614
*/
615
interface SnapshotStateList<T> : MutableList<T>, State<List<T>>
616
617
/**
618
* A map that participates in snapshot state management.
619
*/
620
interface SnapshotStateMap<K, V> : MutableMap<K, V>, State<Map<K, V>>
621
```
622
623
**Usage Examples:**
624
625
```kotlin
626
// State hoisting pattern
627
@Composable
628
fun TodoApp() {
629
var todos by remember { mutableStateOf(emptyList<Todo>()) }
630
var newTodoText by remember { mutableStateOf("") }
631
632
Column {
633
TodoInput(
634
text = newTodoText,
635
onTextChange = { newTodoText = it },
636
onAddTodo = {
637
if (newTodoText.isNotBlank()) {
638
todos = todos + Todo(id = generateId(), text = newTodoText)
639
newTodoText = ""
640
}
641
}
642
)
643
644
TodoList(
645
todos = todos,
646
onToggleTodo = { todoId ->
647
todos = todos.map { todo ->
648
if (todo.id == todoId) {
649
todo.copy(completed = !todo.completed)
650
} else {
651
todo
652
}
653
}
654
},
655
onDeleteTodo = { todoId ->
656
todos = todos.filter { it.id != todoId }
657
}
658
)
659
}
660
}
661
662
@Composable
663
fun TodoInput(
664
text: String,
665
onTextChange: (String) -> Unit,
666
onAddTodo: () -> Unit
667
) {
668
Row {
669
TextField(
670
value = text,
671
onValueChange = onTextChange,
672
label = { Text("New todo") },
673
modifier = Modifier.weight(1f)
674
)
675
Button(onClick = onAddTodo) {
676
Text("Add")
677
}
678
}
679
}
680
681
@Composable
682
fun TodoList(
683
todos: List<Todo>,
684
onToggleTodo: (String) -> Unit,
685
onDeleteTodo: (String) -> Unit
686
) {
687
LazyColumn {
688
items(todos) { todo ->
689
TodoItem(
690
todo = todo,
691
onToggle = { onToggleTodo(todo.id) },
692
onDelete = { onDeleteTodo(todo.id) }
693
)
694
}
695
}
696
}
697
698
// Using SnapshotStateList for collection state
699
@Composable
700
fun StateListExample() {
701
val items = remember { mutableStateListOf<String>() }
702
703
Column {
704
Button(onClick = {
705
items.add("Item ${items.size + 1}")
706
}) {
707
Text("Add Item")
708
}
709
710
LazyColumn {
711
itemsIndexed(items) { index, item ->
712
Row {
713
Text(item, modifier = Modifier.weight(1f))
714
Button(onClick = { items.removeAt(index) }) {
715
Text("Remove")
716
}
717
}
718
}
719
}
720
}
721
}
722
723
data class Todo(val id: String, val text: String, val completed: Boolean = false)
724
fun generateId(): String = UUID.randomUUID().toString()
725
```