0
# Effects and Lifecycle
1
2
Side effect APIs for integrating with external systems, managing resources, and handling component lifecycle in Compose. Effects allow you to perform operations that go beyond the pure functional nature of composables.
3
4
## Capabilities
5
6
### Side Effects
7
8
Execute code that runs on every recomposition.
9
10
```kotlin { .api }
11
/**
12
* Executes a side effect on every recomposition
13
* Use sparingly as it runs on every recomposition
14
* @param effect The side effect to execute
15
*/
16
@Composable
17
fun SideEffect(effect: () -> Unit)
18
```
19
20
**Usage Examples:**
21
22
```kotlin
23
import androidx.compose.runtime.*
24
import androidx.compose.runtime.produceState
25
import androidx.compose.runtime.rememberCoroutineScope
26
27
@Composable
28
fun SideEffectExample(analytics: Analytics) {
29
val screenName = "UserProfile"
30
31
// Runs on every recomposition
32
SideEffect {
33
analytics.trackScreenView(screenName)
34
}
35
36
Text("User Profile Screen")
37
}
38
39
@Composable
40
fun LoggingExample() {
41
var count by remember { mutableStateOf(0) }
42
43
// Logs every time count changes and composable recomposes
44
SideEffect {
45
println("Recomposed with count: $count")
46
}
47
48
Button(onClick = { count++ }) {
49
Text("Count: $count")
50
}
51
}
52
```
53
54
### Launched Effects
55
56
Launch coroutines that are tied to the composable's lifecycle.
57
58
```kotlin { .api }
59
/**
60
* Launches a coroutine in the composition scope
61
* Coroutine is cancelled when the composable leaves composition or keys change
62
* @param keys Dependency keys - effect restarts when any key changes
63
* @param block Suspending block to execute
64
*/
65
@Composable
66
fun LaunchedEffect(
67
vararg keys: Any?,
68
block: suspend CoroutineScope.() -> Unit
69
)
70
```
71
72
**Usage Examples:**
73
74
```kotlin
75
@Composable
76
fun LaunchedEffectExample(userId: String) {
77
var userData by remember { mutableStateOf<UserData?>(null) }
78
79
// Launch effect that restarts when userId changes
80
LaunchedEffect(userId) {
81
userData = null // Show loading
82
try {
83
userData = userRepository.getUser(userId)
84
} catch (e: Exception) {
85
userData = UserData.Error(e.message)
86
}
87
}
88
89
when (val data = userData) {
90
null -> CircularProgressIndicator()
91
is UserData.Success -> UserProfile(data.user)
92
is UserData.Error -> ErrorMessage(data.message)
93
}
94
}
95
96
@Composable
97
fun AnimationEffect() {
98
var animationState by remember { mutableStateOf(0f) }
99
100
// Animate continuously
101
LaunchedEffect(Unit) {
102
while (true) {
103
animationState = (animationState + 0.01f) % 1f
104
delay(16) // ~60 FPS
105
}
106
}
107
108
CustomAnimatedComponent(progress = animationState)
109
}
110
```
111
112
### Disposable Effects
113
114
Effects with cleanup capabilities when the composable leaves composition or keys change.
115
116
```kotlin { .api }
117
/**
118
* Effect that requires cleanup when keys change or component leaves composition
119
* @param keys Dependency keys - effect restarts when any key changes
120
* @param effect Effect block that returns a DisposableEffectResult
121
*/
122
@Composable
123
fun DisposableEffect(
124
vararg keys: Any?,
125
effect: DisposableEffectScope.() -> DisposableEffectResult
126
)
127
128
/**
129
* Scope for disposable effects
130
*/
131
interface DisposableEffectScope {
132
fun onDispose(onDisposeEffect: () -> Unit): DisposableEffectResult
133
}
134
135
/**
136
* Result of a disposable effect providing cleanup callback
137
*/
138
interface DisposableEffectResult
139
```
140
141
**Usage Examples:**
142
143
```kotlin
144
@Composable
145
fun DisposableEffectExample() {
146
val lifecycleOwner = LocalLifecycleOwner.current
147
148
DisposableEffect(lifecycleOwner) {
149
val observer = LifecycleEventObserver { _, event ->
150
when (event) {
151
Lifecycle.Event.ON_START -> println("Component started")
152
Lifecycle.Event.ON_STOP -> println("Component stopped")
153
else -> {}
154
}
155
}
156
157
lifecycleOwner.lifecycle.addObserver(observer)
158
159
onDispose {
160
lifecycleOwner.lifecycle.removeObserver(observer)
161
}
162
}
163
}
164
165
@Composable
166
fun KeyboardListenerExample() {
167
DisposableEffect(Unit) {
168
val keyListener = object : KeyListener {
169
override fun keyPressed(e: KeyEvent) {
170
println("Key pressed: ${e.keyCode}")
171
}
172
}
173
174
KeyboardFocusManager.getCurrentKeyboardFocusManager()
175
.addKeyEventDispatcher(keyListener)
176
177
onDispose {
178
KeyboardFocusManager.getCurrentKeyboardFocusManager()
179
.removeKeyEventDispatcher(keyListener)
180
}
181
}
182
}
183
```
184
185
### Coroutine Scope Management
186
187
Get a coroutine scope that survives recompositions for launching coroutines from event handlers.
188
189
```kotlin { .api }
190
/**
191
* Returns a CoroutineScope that is cancelled when the composable leaves composition
192
* Use this for launching coroutines from event handlers like onClick
193
* @return CoroutineScope tied to the composable's lifecycle
194
*/
195
@Composable
196
fun rememberCoroutineScope(): CoroutineScope
197
```
198
199
**Usage Examples:**
200
201
```kotlin
202
@Composable
203
fun CoroutineScopeExample() {
204
val scope = rememberCoroutineScope()
205
var result by remember { mutableStateOf("") }
206
207
Button(
208
onClick = {
209
// Launch coroutine from event handler
210
scope.launch {
211
result = "Loading..."
212
delay(2000)
213
result = apiService.fetchData()
214
}
215
}
216
) {
217
Text("Fetch Data")
218
}
219
220
Text("Result: $result")
221
}
222
223
@Composable
224
fun ScrollToTopExample() {
225
val listState = rememberLazyListState()
226
val scope = rememberCoroutineScope()
227
228
Column {
229
Button(
230
onClick = {
231
scope.launch {
232
listState.animateScrollToItem(0)
233
}
234
}
235
) {
236
Text("Scroll to Top")
237
}
238
239
LazyColumn(state = listState) {
240
items(100) { index ->
241
Text("Item $index")
242
}
243
}
244
}
245
}
246
```
247
248
### State Production from Async Sources
249
250
Create state from asynchronous data sources.
251
252
```kotlin { .api }
253
/**
254
* Produces state from a suspending producer function
255
* @param initialValue Initial value while producer is running
256
* @param keys Dependency keys - producer restarts when any key changes
257
* @param producer Suspending function that produces values
258
* @return State holding the produced value
259
*/
260
@Composable
261
fun <T> produceState(
262
initialValue: T,
263
vararg keys: Any?,
264
producer: suspend ProduceStateScope<T>.() -> Unit
265
): State<T>
266
267
/**
268
* Scope for producing state values
269
*/
270
interface ProduceStateScope<T> : MutableState<T>, CoroutineScope {
271
suspend fun awaitDispose(onDispose: () -> Unit)
272
}
273
```
274
275
**Usage Examples:**
276
277
```kotlin
278
@Composable
279
fun ProduceStateExample(url: String) {
280
val imageState by produceState<ImageBitmap?>(null, url) {
281
value = loadImageFromNetwork(url)
282
}
283
284
when (val image = imageState) {
285
null -> CircularProgressIndicator()
286
else -> Image(bitmap = image, contentDescription = null)
287
}
288
}
289
290
@Composable
291
fun NetworkDataExample(endpoint: String) {
292
val networkData by produceState(
293
initialValue = NetworkState.Loading,
294
key1 = endpoint
295
) {
296
try {
297
val data = httpClient.get(endpoint)
298
value = NetworkState.Success(data)
299
} catch (e: Exception) {
300
value = NetworkState.Error(e.message ?: "Unknown error")
301
}
302
303
awaitDispose {
304
// Cleanup network resources
305
httpClient.cancel()
306
}
307
}
308
309
when (networkData) {
310
is NetworkState.Loading -> LoadingIndicator()
311
is NetworkState.Success -> DataDisplay(networkData.data)
312
is NetworkState.Error -> ErrorMessage(networkData.message)
313
}
314
}
315
```
316
317
### Flow Integration
318
319
Convert Flow/StateFlow to Compose State.
320
321
```kotlin { .api }
322
/**
323
* Collects values from a Flow and represents them as State
324
* @param initial Initial value to use before first emission
325
* @param context CoroutineContext for collection (default: EmptyCoroutineContext)
326
* @return State that updates with Flow emissions
327
*/
328
@Composable
329
fun <T> Flow<T>.collectAsState(
330
initial: T,
331
context: CoroutineContext = EmptyCoroutineContext
332
): State<T>
333
334
/**
335
* Collects values from a StateFlow and represents them as State
336
* Uses the StateFlow's current value as initial value
337
*/
338
@Composable
339
fun <T> StateFlow<T>.collectAsState(
340
context: CoroutineContext = EmptyCoroutineContext
341
): State<T>
342
```
343
344
**Usage Examples:**
345
346
```kotlin
347
@Composable
348
fun FlowExample(viewModel: MyViewModel) {
349
// Collect from Flow
350
val uiState by viewModel.uiStateFlow.collectAsState()
351
352
// Collect from Flow with initial value
353
val searchResults by viewModel.searchFlow.collectAsState(
354
initial = emptyList()
355
)
356
357
// Collect with custom context
358
val backgroundData by viewModel.backgroundFlow.collectAsState(
359
initial = null,
360
context = Dispatchers.IO
361
)
362
363
when (uiState) {
364
is UiState.Loading -> LoadingScreen()
365
is UiState.Success -> SuccessScreen(uiState.data)
366
is UiState.Error -> ErrorScreen(uiState.message)
367
}
368
}
369
370
@Composable
371
fun DatabaseExample(database: UserDatabase) {
372
val users by database.getAllUsers().collectAsState(initial = emptyList())
373
374
LazyColumn {
375
items(users) { user ->
376
UserItem(user = user)
377
}
378
}
379
}
380
```
381
382
## Advanced Effect Patterns
383
384
### Effect Cleanup Patterns
385
386
Common patterns for cleaning up resources in effects.
387
388
```kotlin
389
@Composable
390
fun ResourceManagementExample() {
391
DisposableEffect(Unit) {
392
val resource = acquireResource()
393
394
onDispose {
395
resource.release()
396
}
397
}
398
399
LaunchedEffect(Unit) {
400
val connection = openConnection()
401
try {
402
// Use connection
403
handleConnection(connection)
404
} finally {
405
connection.close()
406
}
407
}
408
}
409
```
410
411
### Conditional Effects
412
413
Effects that run conditionally based on state.
414
415
```kotlin
416
@Composable
417
fun ConditionalEffectExample() {
418
var isActive by remember { mutableStateOf(false) }
419
420
if (isActive) {
421
LaunchedEffect(Unit) {
422
startPeriodicUpdate()
423
}
424
}
425
426
// Alternative pattern
427
LaunchedEffect(isActive) {
428
if (isActive) {
429
startService()
430
}
431
}
432
}
433
```