[Jetpack Compose] 3. Architecture and state(2) #Side Effects #Saver
📌참고자료
Advanced State and Side Effects in Jetpack Compose
📌참고자료
- Side Effect in Compose:
a change to the state of the app that happens outside the scope of a composable function
- composables should ideally be side-effect free
-> composable의 생명주기 & 특징 때문
- unpredictable recompositions
- executing recompositions in different orders
- recompositions can be discarded
- sometimes, side-effects are necessary
- ex. triggering on-off event (show snackbars, navigate screens)
- these actions should be called from a controlled environment
-> use Effect API
to execute side-effects in a predictable manner
effect
:
a composable function that doesn't emit UI and causes side effects to run when composition completes
➖
LaunchedEffect
- run suspend functions in scope of a composable
LaunchedEffect
is a composable function
-> can only be used in other composable functions
LaunchedEffect
enters composition -> launches a coroutine
LaunchedEffect
leaves composition -> cancels the coroutine
LaunchedEffect
recomposed with different keys -> cancel existing coroutine & launch new coroutine
rememberUpdatedState
- reference a value in an effect that shouldn't restart if the value changes
@Composable
fun <T> rememberUpdatedState(newValue: T): State<T> = remember {
mutableStateOf(newValue)
}.apply { value = newValue }
@Composable
fun TimerExample(timer: Timer) {
val currentTime by timer.currentTime.collectAsState()
val onTimeout = rememberUpdatedState(newValue = {
})
LaunchedEffect(currentTime) {
if (currentTime >= TIMEOUT_DURATION) {
onTimeout.value()
}
}
Text(text = "Time: $currentTime")
}
rememberCoroutineScope
- launch a coroutine outside of a composable, but scoped with the composable
-> automatically canceled once it leaves the composition
rememberCoroutineScope
는 자신을 호출한 Composition의 CoroutineScope
를 반환하는 composable function
DisposableEffect
- effects that require cleanup
- 블록 맨 마지막에
onDispose
절 반드시 포함해야
- 빈 블록의
onDispose
는 좋은 practice가 아님 -> 더 적절한 다른 effect 사용하기
SideEffect
- share Compose state with non-Compose code
- 성공적인 recomposition이 실행돨 때 마다 effect가 실행되도록 보장됨
= recomposition의 성공이 보장되지 않는 상태에서 effect 실행되지 X
produceState
- convert non-Compose state into Compose state
- ex. external subscription-driven state(ex. Flow, LiveData, RxJava)
- can also be used to observe non-suspending sources of data
produceState
enters composition -> producer is launched
produceState
leaves composition -> producer is cancelled
- ViewModel에서 UI state를 관리하는 것이 권장되지만,
UI state이 매우 간단하다면 composable 내부에서 produceState
을 사용하는 것이 best alternative
snapshotFlow
- convert Compose's state object into cold Flow
snapshotFlow
블록에 터미널 연산자(collect, collectLast, 등) 불림
-> 블록 내부 State object들의 값을 emit
snapshotFlow
블록 내부 State object 값 변경됨
-> Flow will emit new value to its collector
derivedStateOf
- convert one or multiple state objects into another state
- use when inputs to a composable are changing more often than you need to recompose
derivedStateOf
creates new Compose state object
-> to execute recomposition only when condition toggles
- expensive
➖
Saver
:
describes how the object of Original class can be simplified & converted into something Saveable
restore(value: Saveable): Original?
SaverScope.save(value: Original): Saveable?
- Savable types:
- defined by
SaveableStateRegistry
- by default,
Bundle
클래스에 저장 가능한 타입은 다 저장 가능
- SaveableStateRegistry interface
canBeSaved(value: Any): Boolean
value가 해당 Registry에 저장 가능한지 반환
consumeRestored(key: String): Any?
key에 대해 저장된 값 반환
performSave(): Map<String, List<Any?>>
executes all the registered value providers -> map으로 combine하여 반환
registerProvider(key: String, valueProvider: () -> Any):SaveableStateRegistry.Entry
register value provider