Side Effect (부수 효과)
- Composable 함수를 벗어난 곳에서 앱의 state(상태) 변경이 일어나는 것
- Composable 은 Side Effect 가 없는 것이 좋으나, 앱 상태를 변경해야 하는 경우 Side Effect 를 예측 가능한 방식으로 실행되도록 Effect API 를 사용해야 한다.
- 한 번만 일어나는 UI 이벤트로 변경 사항이 state 로 관리될 필요가 없는 경우 (SnackBar, ToastMessage 등)
- 다른 Screen 으로 이동하는 Navigation (사용자 인터랙션(예시: 버튼 클릭)에 의해 발생하는 경우 필요 없음)
- system services 들과 상호작용 하는 것
- Coroutine 을 이용한 네트워킹이나 디스크 IO
1. LaunchedEffect
@Composable
@NonRestartableComposable
fun LaunchedEffect(key1: Any?, block: suspend CoroutineScope.() -> Unit): Unit
- Composable 함수 안에서 Coroutine 의 suspend 함수를 실행할 수 있도록 해준다.
- 아래 3가지 경우에 따라 LaunchedEffect 의 Coroutine 이 launch 된다.
- Composable composition 시작
- state 값의 변화에 따른 Composable 의 recomposition 시작
- LaunchedEffect 의 인자로 들어오는 key 값 변화
- key 값은 Any 타입이며, vararg 타입으로 오버로딩된 함수가 존재하기 때문에 개수에 상관없이 여러 개의 키 값을 사용할 수 있다.
- composition 이 종료되면 Coroutine 은 Cancel 이 되고, recompose가 되거나 key 값이 변화하면 다시 launch 된다.
- 한 번만 실행되게 하려면 key 값에 변화를 주지 않기 위해 Unit 이나 Boolean 의 ture 같이 static 한 값을 인자로 넣어준다.
- flow 를 수집하는 곳에서는 launchedEffect 를 반복해서 실행할 필요가 없으며, 한 번만 실행해 두면 flow 의 emit 이 트리거가 되어 side effect 가 반응하게 된다.
- 사용자 인터랙션에 사용하기에는 어려움이 있다.
- key 값이 변하거나 Composable 이 composition 을 떠나게 되면 사용자 인터랙션과는 관계없이 Coroutine 이 Cancel 되고 초기화되기 때문에 원하지 않는 타이밍에 Coroutine 안의 값이 초기화될 수 있다.
- key 값이 변하지 않더라도(Unit) Composition 의 초기 단계에서 한 번만 실행된다.
- 사용자 인터랙션과 관련된 비동기 작업을 수행할 때는 rememberCoroutineScope 를 사용하는 것이 좋다.
remember vs rememberUpdatedState
- remember : Composable 이 처음 호출될 때 한 번만 실행되어 초기 값을 설정하고, 이후에는 그 값을 유지한다.
- rememberUpdatedState : 값이 변경되는 경우, recomposition 이 발생하지 않아야 하는 상황에서 최신 값을 참조하기 위해 사용한다.
@Composable
fun LaunchedScreen() {
var textState by remember { mutableStateOf("Default") } // Def (by recomposition)
Column {
TextField(
value = textState,
onValueChange = { textState = it } // Def
)
RememberUpdateTestText(textState)
}
}
@Composable
fun RememberUpdateTestText(text: String) {
val rememberText by remember { mutableStateOf(text) } // Default
val rememberUpdatedText by rememberUpdatedState(text) // Def
Text("RememberText : $rememberText")
Text("UpdatedText : $rememberUpdatedText")
}
2. rememberCoroutineScope
@Composable
inline fun rememberCoroutineScope(
crossinline getContext: @DisallowComposableCalls () -> CoroutineContext = {
EmptyCoroutineContext
}
): CoroutineScope
- *Composable 함수 밖에서 state 에 영향을 줄 수 있는 Coroutine 을 실행할 수 있도록 해준다.
- Composable 함수 밖 : Composable 의 범위를 벗어나는 곳 (예시: onClick 의 람다 함수 내)
- *Composable 의 생명 주기에 따라 Coroutine 의 launch 와 cancel 를 관리해 준다.
- Composable 의 생명 주기에 따라 : Composable 이 재구성되거나 구성에서 떠날 때, 해당 CoroutineScope 내에서 실행된 Coroutine 은 자동으로 취소되지만, 이 스코프를 통해 실행된 Coroutine 은 독립적으로 관리된다.
3. DisposableEffect
@Composable
@NonRestartableComposable
fun DisposableEffect(key1: Any?, effect: DisposableEffectScope.() -> DisposableEffectResult): Unit
- LaunchedEffect 와 유사한 역할을 하지만, 재구성이나 종료 시, 즉 생명주기가 끝나는 시점에 onDispose 함수가 호출된다.
- onDispose 블록이 없는 경우 오류가 발생하며, Composable 이 종료될 때 리소스의 해제와 같은 작업에 사용된다.
4. SideEffect
@Composable
@NonRestartableComposable
@ExplicitGroupsComposable
fun SideEffect(effect: () -> Unit): Unit
- 최초에 구성, 재구성 등 Composable 이 성공적으로 완료되면 해당 블록이 호출된다.
5. produceState
@Composable
fun <T : Any?> produceState(initialValue: T, producer: suspend ProduceStateScope<T>.() -> Unit): State<T>
- 비 Compose 상태를 Composable 상태로 변환할 때 사용하는 코루틴이다.
- Flow, LiveData, RxJava 등 Composable 외부에서 사용되는 것들을 produceState 블록 내부에서 사용하고, 이 값을 기반으로 연산한 후, Composable 에서 사용할 수 있는 형태인 State<T>로 반환하여 필요한 데이터를 Composable 에서 사용할 수 있도록 해준다.
- 실행된 produceState 가 종료될 때 호출되는 awaitDispose 블록이 있다.
6. derivedStateOf
@StateFactoryMarker
fun <T : Any?> derivedStateOf(calculation: () -> T): State<T>
- remember 블록 안에서 사용되며, 계산에서 사용되는 상태 중 하나가 변경될 때만 계산이 실행된다.
- 블록의 마지막 라인을 State<T> 타입으로 반환한다.
- remember(key) { ... } 형태로 선언하는 경우, key 값이 변경될 때 이하의 블록이 실행된다.
val showButton by remember {
derivedStateOf {
scrollState.firstVisibleItemIndex > 0
}
}
- derivedStateOf 에 의해 상태가 업데이트되는 경우에는 재구성이 발생하지 않는다.
- key 값이 변경되는 경우에는 새로운 derivedStateOf 가 생성되면서 재구성이 발생한다.
7. snapshotFlow
- State<T> 객체를 Flow 로 반환시켜주기 때문에, State<T> 객체를 Flow 로 사용해야 할 때 사용한다.
var showButton by remember { mutableStateOf(false) }
LaunchedEffect(scrollState) {
snapshotFlow { scrollState.firstVisibleItemIndex }
.map { index -> index > 0 }
.distinctUntilChanged() // 새로운 값이 이전의 값과 차이가 있을 경우에만 감지할 수 있도록 도와준다.
.collect {
showButton = it
}
}
- Flow 에서 사용되는 collect 는 코루틴 상에서 동작해야 하기 때문에, LaunchedEffect 를 통해 코루틴 상에서 동작할 수 있도록 감싸준다.
LaunchedEffect , Side Effect 그리고 rememberCoroutine 정리
오늘은 Jetpack Compose 의 Side Effect 와 LaunchedEffect, 그리고 rememberCoroutine 에 대해서 정리해 보도록 하겠습니다. 1. Side Effect Side effect 의 단어 뜻은 원래 부차적이고, 의도하지 않은 효과를 말하는데요.
developer88.tistory.com
[Jetpack] Compose 사용하기 - 2. Side Effect와 Coroutine 1
본 게시글은 이전 글에 이어서 작성된 부분입니다.2022.06.07 - [Android/Jetpack] - [Jetpack] Compose 사용하기 - 1. remember와 MutableState 기존의 코드에서도 코루틴을 많이 사용하기 때문에, Compose를 사용할 때
heegs.tistory.com
[Jetpack] Compose 사용하기 - 2. Side Effect와 Coroutine 2
본 게시글은 이전 글에 이어서 작성된 부분입니다.2022.06.07 - [Android/Jetpack] - [Jetpack] Compose 사용하기 - 1. remember와 MutableState2022.06.10 - [Android/Jetpack] - [Jetpack] Compose 사용하기 - 2. Side Effect와 Coroutine 1
heegs.tistory.com
'안드로이드 > Compose' 카테고리의 다른 글
[Compose] TextField에서 키보드 hide 처리하기 (0) | 2024.11.23 |
---|---|
[Compose] TextField에서 엔터키 사용하기 (0) | 2024.11.21 |
[Compose] 목록 및 그리드 (0) | 2024.11.17 |