or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

composables.mdcore-ui.mdgraphics.mdindex.mdinput.mdios-integration.mdlayout.mdmaterial-design.mdresources.mdstate.mdtext.mdwindow.md

state.mddocs/

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

```