0
# Effects
1
2
Side effect management with lifecycle-aware execution and automatic cleanup. Effects provide safe ways to perform operations outside the composition scope while integrating properly with the Compose lifecycle.
3
4
## Capabilities
5
6
### DisposableEffect
7
8
Effect that requires cleanup when the effect leaves the composition or its keys change.
9
10
```kotlin { .api }
11
/**
12
* Effect that runs when keys change and provides cleanup via DisposableEffectResult
13
* @param key1 Key that determines when effect should restart
14
* @param effect Lambda that runs the effect and returns cleanup
15
*/
16
@Composable
17
fun DisposableEffect(
18
key1: Any?,
19
effect: DisposableEffectScope.() -> DisposableEffectResult
20
): Unit
21
22
/**
23
* DisposableEffect with two key dependencies
24
* @param key1 First key dependency
25
* @param key2 Second key dependency
26
* @param effect Lambda that runs the effect and returns cleanup
27
*/
28
@Composable
29
fun DisposableEffect(
30
key1: Any?,
31
key2: Any?,
32
effect: DisposableEffectScope.() -> DisposableEffectResult
33
): Unit
34
35
/**
36
* DisposableEffect with three key dependencies
37
* @param key1 First key dependency
38
* @param key2 Second key dependency
39
* @param key3 Third key dependency
40
* @param effect Lambda that runs the effect and returns cleanup
41
*/
42
@Composable
43
fun DisposableEffect(
44
key1: Any?,
45
key2: Any?,
46
key3: Any?,
47
effect: DisposableEffectScope.() -> DisposableEffectResult
48
): Unit
49
50
/**
51
* DisposableEffect with multiple key dependencies
52
* @param keys Variable number of key dependencies
53
* @param effect Lambda that runs the effect and returns cleanup
54
*/
55
@Composable
56
fun DisposableEffect(
57
vararg keys: Any?,
58
effect: DisposableEffectScope.() -> DisposableEffectResult
59
): Unit
60
61
/**
62
* Scope for DisposableEffect providing cleanup mechanism
63
*/
64
interface DisposableEffectScope {
65
/**
66
* Creates a cleanup handler
67
* @param onDispose Function called when effect is disposed
68
* @return DisposableEffectResult for the effect
69
*/
70
fun onDispose(onDispose: () -> Unit): DisposableEffectResult
71
}
72
73
/**
74
* Result of a DisposableEffect containing cleanup logic
75
*/
76
interface DisposableEffectResult
77
```
78
79
**Usage Examples:**
80
81
```kotlin
82
@Composable
83
fun DisposableEffectExample(userId: String) {
84
// Effect that depends on userId
85
DisposableEffect(userId) {
86
val subscription = userService.subscribeToUser(userId) { user ->
87
// Handle user updates
88
}
89
90
// Cleanup when userId changes or composable leaves composition
91
onDispose {
92
subscription.cancel()
93
}
94
}
95
96
// iOS-specific example: Observer pattern
97
DisposableEffect(Unit) {
98
val observer = NotificationCenter.default.addObserver(
99
forName: UIApplication.didBecomeActiveNotification,
100
object: null,
101
queue: OperationQueue.main
102
) { _ ->
103
// Handle app becoming active
104
}
105
106
onDispose {
107
NotificationCenter.default.removeObserver(observer)
108
}
109
}
110
}
111
```
112
113
### LaunchedEffect
114
115
Effect that launches a coroutine and automatically cancels it when the effect leaves the composition.
116
117
```kotlin { .api }
118
/**
119
* Launches a coroutine in the composition scope
120
* @param key1 Key that determines when to restart the coroutine
121
* @param block Suspending function to execute in the coroutine
122
*/
123
@Composable
124
fun LaunchedEffect(
125
key1: Any?,
126
block: suspend CoroutineScope.() -> Unit
127
): Unit
128
129
/**
130
* LaunchedEffect with two key dependencies
131
* @param key1 First key dependency
132
* @param key2 Second key dependency
133
* @param block Suspending function to execute in the coroutine
134
*/
135
@Composable
136
fun LaunchedEffect(
137
key1: Any?,
138
key2: Any?,
139
block: suspend CoroutineScope.() -> Unit
140
): Unit
141
142
/**
143
* LaunchedEffect with three key dependencies
144
* @param key1 First key dependency
145
* @param key2 Second key dependency
146
* @param key3 Third key dependency
147
* @param block Suspending function to execute in the coroutine
148
*/
149
@Composable
150
fun LaunchedEffect(
151
key1: Any?,
152
key2: Any?,
153
key3: Any?,
154
block: suspend CoroutineScope.() -> Unit
155
): Unit
156
157
/**
158
* LaunchedEffect with multiple key dependencies
159
* @param keys Variable number of key dependencies
160
* @param block Suspending function to execute in the coroutine
161
*/
162
@Composable
163
fun LaunchedEffect(
164
vararg keys: Any?,
165
block: suspend CoroutineScope.() -> Unit
166
): Unit
167
```
168
169
**Usage Examples:**
170
171
```kotlin
172
@Composable
173
fun LaunchedEffectExample(searchQuery: String) {
174
var searchResults by remember { mutableStateOf<List<SearchResult>>(emptyList()) }
175
176
// Launch search when query changes
177
LaunchedEffect(searchQuery) {
178
if (searchQuery.isNotEmpty()) {
179
delay(300) // Debounce
180
try {
181
searchResults = searchService.search(searchQuery)
182
} catch (e: Exception) {
183
// Handle error
184
}
185
}
186
}
187
188
// Launch periodic update
189
LaunchedEffect(Unit) {
190
while (true) {
191
delay(30_000) // 30 seconds
192
refreshData()
193
}
194
}
195
}
196
197
@Composable
198
fun NetworkStatusExample() {
199
var isOnline by remember { mutableStateOf(true) }
200
201
LaunchedEffect(Unit) {
202
networkMonitor.status.collect { status ->
203
isOnline = status == NetworkStatus.Connected
204
}
205
}
206
}
207
```
208
209
### SideEffect
210
211
Effect that executes after every successful recomposition.
212
213
```kotlin { .api }
214
/**
215
* Executes a side effect after every recomposition
216
* @param effect Function to execute after recomposition
217
*/
218
@Composable
219
fun SideEffect(effect: () -> Unit): Unit
220
```
221
222
**Usage Examples:**
223
224
```kotlin
225
@Composable
226
fun SideEffectExample(user: User) {
227
// Log every time this composable recomposes
228
SideEffect {
229
logger.log("UserProfile recomposed for user: ${user.id}")
230
}
231
232
// Update analytics after recomposition
233
SideEffect {
234
analytics.track("profile_viewed", mapOf("user_id" to user.id))
235
}
236
}
237
238
@Composable
239
fun UIKitIntegrationExample() {
240
val currentUser by viewModel.currentUser.collectAsState()
241
242
// Update UIKit view after recomposition
243
SideEffect {
244
navigationController.title = currentUser?.name ?: "Profile"
245
}
246
}
247
```
248
249
### rememberCoroutineScope
250
251
Returns a CoroutineScope that's automatically cancelled when the composition leaves.
252
253
```kotlin { .api }
254
/**
255
* Returns a CoroutineScope bound to the composition lifecycle
256
* @return CoroutineScope that's cancelled when composition is disposed
257
*/
258
@Composable
259
fun rememberCoroutineScope(): CoroutineScope
260
261
/**
262
* Returns a CoroutineScope bound to the composition lifecycle with custom context
263
* @param getContext Function to provide custom coroutine context
264
* @return CoroutineScope with custom context that's cancelled when composition is disposed
265
*/
266
@Composable
267
fun rememberCoroutineScope(getContext: () -> CoroutineContext): CoroutineScope
268
```
269
270
**Usage Examples:**
271
272
```kotlin
273
@Composable
274
fun CoroutineScopeExample() {
275
val scope = rememberCoroutineScope()
276
val snackbarHostState = remember { SnackbarHostState() }
277
278
Column {
279
Button(onClick = {
280
// Launch coroutine in response to user action
281
scope.launch {
282
try {
283
val result = performNetworkCall()
284
snackbarHostState.showSnackbar("Success: $result")
285
} catch (e: Exception) {
286
snackbarHostState.showSnackbar("Error: ${e.message}")
287
}
288
}
289
}) {
290
Text("Perform Action")
291
}
292
293
SnackbarHost(hostState = snackbarHostState)
294
}
295
}
296
297
@Composable
298
fun CustomCoroutineScopeExample() {
299
// Custom scope with IO dispatcher for background work
300
val ioScope = rememberCoroutineScope { Dispatchers.IO }
301
302
// Custom scope for iOS main thread operations
303
val mainScope = rememberCoroutineScope { IOSMainDispatcher }
304
305
Button(onClick = {
306
// Heavy computation on IO dispatcher
307
ioScope.launch {
308
val data = performHeavyComputation()
309
310
// Switch to main thread for UI updates
311
mainScope.launch {
312
updateUI(data)
313
}
314
}
315
}) {
316
Text("Process Data")
317
}
318
}
319
```
320
321
### rememberUpdatedState
322
323
Remembers a value that's always up-to-date in long-running effects.
324
325
```kotlin { .api }
326
/**
327
* Remembers a value that's always current in long-running operations
328
* @param newValue The current value to remember
329
* @return State containing the most recent value
330
*/
331
@Composable
332
fun <T> rememberUpdatedState(newValue: T): State<T>
333
```
334
335
**Usage Examples:**
336
337
```kotlin
338
@Composable
339
fun TimerExample(onTimeout: () -> Unit) {
340
// Remember the latest callback to avoid stale captures
341
val currentOnTimeout by rememberUpdatedState(onTimeout)
342
343
LaunchedEffect(Unit) {
344
delay(5000) // 5 second timer
345
currentOnTimeout() // This will call the latest onTimeout
346
}
347
}
348
349
@Composable
350
fun IntervalExample(interval: Long, action: () -> Unit) {
351
val currentAction by rememberUpdatedState(action)
352
353
LaunchedEffect(interval) {
354
while (true) {
355
delay(interval)
356
currentAction() // Always uses the latest action
357
}
358
}
359
}
360
```
361
362
## Advanced Effect Patterns
363
364
### Effect Error Handling
365
366
```kotlin
367
@Composable
368
fun ErrorHandlingExample() {
369
var error by remember { mutableStateOf<String?>(null) }
370
371
LaunchedEffect(Unit) {
372
try {
373
performRiskyOperation()
374
} catch (e: Exception) {
375
error = e.message
376
}
377
}
378
379
error?.let { errorMessage ->
380
Text("Error: $errorMessage", color = Color.Red)
381
}
382
}
383
```
384
385
### Effect Cancellation
386
387
```kotlin
388
@Composable
389
fun CancellationExample(shouldRun: Boolean) {
390
LaunchedEffect(shouldRun) {
391
if (shouldRun) {
392
try {
393
while (true) {
394
performPeriodicTask()
395
delay(1000)
396
}
397
} catch (e: CancellationException) {
398
// Cleanup on cancellation
399
cleanup()
400
throw e // Re-throw to properly handle cancellation
401
}
402
}
403
}
404
}
405
```
406
407
### Complex Effect Dependencies
408
409
```kotlin
410
@Composable
411
fun ComplexDependenciesExample(
412
userId: String,
413
refreshTrigger: Int,
414
settings: UserSettings
415
) {
416
// Effect runs when any dependency changes
417
LaunchedEffect(userId, refreshTrigger, settings.theme) {
418
loadUserData(userId, settings)
419
}
420
}
421
```
422
423
## iOS Platform Considerations
424
425
### Main Thread Integration
426
427
```kotlin
428
@Composable
429
fun IOSMainThreadExample() {
430
LaunchedEffect(Unit) {
431
// Switch to main dispatcher for UI updates
432
withContext(Dispatchers.Main.immediate) {
433
updateUIKitView()
434
}
435
}
436
}
437
```
438
439
### Background Task Management
440
441
```kotlin
442
@Composable
443
fun BackgroundTaskExample() {
444
DisposableEffect(Unit) {
445
val taskId = UIApplication.shared.beginBackgroundTask {
446
// Background task expired
447
}
448
449
onDispose {
450
UIApplication.shared.endBackgroundTask(taskId)
451
}
452
}
453
}
454
```
455
456
## Performance Considerations
457
458
- **Effect Keys**: Use appropriate keys to control when effects restart
459
- **Cancellation**: Always handle coroutine cancellation properly in LaunchedEffect
460
- **Resource Cleanup**: Use DisposableEffect for resources that need explicit cleanup
461
- **State Updates**: Use rememberUpdatedState for callbacks in long-running effects
462
- **Thread Safety**: Be mindful of thread safety when accessing mutable state from effects