SideEffect
- Composable 함수는 입력 값이 변경될 때마다 재실행되며, UI를 업데이트. 하지만, UI 업데이트와 직접적인 관련이 없고, Composable 함수의 재실행 시마다 반복적으로 실행될 필요가 없는 작업이 side-effect
- 즉, 상태 값과 직접적으로 관련 없는 UI 작업들이 Composable 함수에서 불필요하게 재호출되는 것을 방지하기 위해 사용
LaunchedEffect : Composable 안에서 suspend func 실행
- 하나 이상의 key를 paramater로 받아야 한다 (ex. error.value)
- key가 바뀌지 않는 이상 같은 코루틴을 사용하고, key가 바뀌면 기존 코루틴을 취소하고 다시 코루틴을 만들어 실행
fun test(
flavors: State<List<Flavor>>,
error: State<Throwable?>,
scaffoldState: ScaffoldState = rememberScaffoldState()
) {
LaunchedEffect(error.value) {
val errorMessage = error.value?.message ?: return@LaunchedEffect
scaffoldState.snackbarHostState.showSnackbar(
message = errorMessage,
actionLabel = "show toast"
)
}
// ... flavors를 사용하여 UI를 구성 ...
}
rememberCoroutineScope
- Composable 내에서 코루틴을 시작하기 위한 방법을 제공하여 더 이상 필요하지 않을 때 적절하게 관리, 취소됨
@Composable
fun MyComposable() {
val scope = rememberCoroutineScope()
val snackbarHostState = remember { SnackbarHostState() }
Button(onClick = {
scope.launch {
snackbarHostState.showSnackbar("Hello from coroutine!")
}
}) {
Text("Show Snackbar")
}
SnackbarHost(hostState = snackbarHostState)
}
rememberUpdateState:
- 값이 변경되더라도 LaunchedEffect가 다시 시작되지 않도록 하면서 최신 값에 접근해야 할 때 사용 즉, LaunchedEffect 내부에서 사용되는 값이 변경되더라도 LaunchedEffect가 다시 시작되지 않아야 하는 경우 (ex. 타이머, 지속되어야하는 애니메이션 같은 경우)에 LaunchedEffect를 다시 시작하지 않고도 최신 값에 접근
- rememberUpdatedState는 값이 변경될 때마다 내부적으로 최신 값을 업데이트하지만, LaunchedEffect의 key로 사용되지 않기 때문에 LaunchedEffect에는 영향 X
@Composable
fun MyScreen(text: String) {
val currentText = rememberUpdatedState(text) // currentText는 MutableState<String> 타입
LaunchedEffect(Unit) {
while (true) {
delay(1000)
val currentTextValue = currentText.value // currentText.value를 통해 최신 값에 접근
println("The latest text is: $currentTextValue")
}
}
Text(text = text)
}
값을 새 값으로 변경해주는 것 밖에 없음..!
DisposableEffect: Composable 함수가 Composition에서 제거될 때 리소스를 해제하기 위해 사용
- lifecycleOwner를 key로 받음
- 리소스 관리에 용이
@Composable
fun MyScreen(lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current) {
DisposableEffect(lifecycleOwner) {
val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_RESUME) {
// 리소스 초기화 또는 시작
} else if (event == Lifecycle.Event.ON_PAUSE) {
// 리소스 해제 또는 중지
}
}
lifecycleOwner.lifecycle.addObserver(observer)
onDispose {
lifecycleOwner.lifecycle.removeObserver(observer)
}
}
}
SideEffect
- 매 recomposition마다 호출됨 (이게 헷갈릴 수 있다)
- Composable 함수가 처음 Composition에 추가될 때, SideEffect 내부의 코드가 실행 O
- Composable 함수가 재구성될 때, SideEffect 내부의 코드는 다시 실행 X
- Composable 함수가 Composition에서 제거될 때, SideEffect 내부의 코드는 실행 X
@Composable
fun MyScreen(count: Int) {
SideEffect {
println("SideEffect called!")
}
Text("Count: $count")
}
SideEffect called!는 한번만 찍힌다..!
즉, 매 Recomposition마다 호출은 되지만 SideEffect { } 내부가 실행되진 않는다!
그래서 로그를 남기거나, analytics 이벤트를 전송하는 작업들이 SideEffect에 넣기 좋다
productState: 비 compose 상태를 compose 상태로 변환
- 비동기적로 상태를 업데이트하고, 그 결과를 State<T>로 반환하는 함수
@Composable
fun MyScreen() {
val counterState = produceState(initialValue = 0) {
while (true) {
delay(1000)
value++
}
}
Text("Counter: ${counterState.value}")
}
ProduceStateScope를 통해 상태를 업데이트..!
- produceState는 내부적으로 remember를 사용하여 mutableStateOf(initialValue)를 생성
- LaunchedEffect(Unit)을 사용하여 비동기 작업을 시작
- producer lambda 내부에서 ProduceStateScope를 통해 상태를 업데이트하면, mutableStateOf의 값이 변경 (예제에선 value ++)
derivedStateOf: Compose에서 다른 상태 값을 기반으로 새로운 상태 값을 계산하여 생성하는 함수
- 상태의 값이 바뀌지 않으면 그 값을 유지 (derivedStateOf는 내부적으로 계산된 값을 캐싱하고 있기 때문에, 값이 같다면 value 변경 X)
- 계산된 값이 바뀔 때만 state의 value가 바뀜 -> 불필요한 재구성 방지: derivedStateOf는 필요한 경우에만 상태 값을 다시 계산하여 업데이트
@Composable
fun MyScreen() {
var counter by remember { mutableStateOf(0) }
val doubledCounter by remember { derivedStateOf { counter * 2 } }
Column {
Button(onClick = { counter++ }) { Text("Increment Counter") }
Text("Counter: $counter")
Text("Doubled Counter: $doubledCounter")
}
}
snapshotFlow
- Compose State를 Flow로 변환하여 State 값의 변경 사항을 비동기적으로 collect하도록 하는 함수
- 이전에 방출한 값과 다를 경우에만 방출 즉, State 값이 변경되면 snapshotFlow는 새로운 값을 Flow로 방출
var counter by remember { mutableStateOf(0) }
LaunchedEffect(Unit) {
snapshotFlow { counter }
.collect { newCounter ->
println("Counter updated: $newCounter")
}
}
Counter updated: 0 만 찍히고 그 후엔 안 찍힘
'🤖 Compose' 카테고리의 다른 글
[Compose] Compose 주의점 (0) | 2025.01.17 |
---|---|
[Compose] Animation (0) | 2025.01.08 |
[Compose] State, StateHoisting (0) | 2025.01.07 |
[Compose] Snackbar (0) | 2025.01.07 |
[Compose] ConstraintLayout, ConstraintSet (0) | 2025.01.07 |