Composable에서의 non-composable
SideEffects란 Composable 함수의 범위 밖에서 발생하는 앱 상태에 관한 변경사항입니다.
Composable의 수명 주기 및 속성으로 인해 SideEffects를 최소화하는 것이 좋습니다.
하지만 스백바를 표시하거나 특정 상태 조건에 따라 다른 화면으로
이동하는 등의 일회성 이벤트를 처리할 때 SideEffects가 필요하게 됩니다.
LaunchedEffect
LaunchedEffect는 컴포저블 범위에서 정지 함수를 실행할 때 사용합니다.
매개변수로 전달 된 코드 블록으로 코루틴이 실행되는데요.
LaunchedEffect가 컴포지션을 종료하면 코루틴이 취소됩니다.
LaunchedEffect(Unit) {
detailViewModel.initAppState(appState,diaryState)
detailViewModel.setDiaryDetail()
}
composition 진입 및 key가 업데이트될 때 실행되며, key가 변경될 때마다 다시 람다를 실행합니다.
위와 같이 Key를 Unit으로 지정할 경우 LaunchedEffect 내의 블록은 싱글턴으로 한 번만 실행되게 됩니다.
when을 활용한 여러 개의 key의 변화에 대한 대응도 지정할 수 있습니다.
rememberCoroutineScope
컴포지션 인식 범위를 확보하여, 컴포저블 외부에서 코루틴을 실행하는 함수입니다.
LaunchedEffect의 경우 Composable 함수 내에서만 사용할 수 있습니다.
반면에 rememberCoroutineScope를 활용한 경우 컴포지션을 종료한 후 자동으로 취소되도록 외부에 선언할 수 있습니다.
코루틴 하나 이상의 수명 주기를 수동으로 관리하는 경우에 사용합니다.
예로 사용자 이벤트가 발생할 때 애니메이션 등을 취소하는 상황이 있을 수 있습니다.
rememberCoroutineScope().launch {
delay(500)
naviController.navigate("ssoScreen")
}
위와 같이 딜레이 후 다음 화면으로 이동하는 코드를 작성하였습니다.
만약에 다른 사용자 이벤트가 발생하거나, 특정 상황으로 취소되는 경우 위 구문이 자동으로 취소되도록 동작하게 됩니다.
주의할 점은 non-composable에서 코루틴을 시작하면, 정의되지 않은 순간에 새 코루틴을 시작할 위험이 있습니다.
rememberUpdatedState
주요 매개변수 중 하나가 변경되면 LaunchedEffect가 다시 시작되게 되는데요,
하지만 경우에 따라 효과에서 값이 변경되면 효과를 다시 시작하지 않을 값을 저장해 둘 수 있습니다.
이러한 기능을 위해서 rememberUpdatedState를 사용하여 캡처하고 업데이트할 수 있는 값의 참조를 만들어야 합니다.
@Composable
fun LandingScreen(onTimeout: () -> Unit) {
// This will always refer to the latest onTimeout function that
// LandingScreen was recomposed with
val currentOnTimeout by rememberUpdatedState(onTimeout)
// Create an effect that matches the lifecycle of LandingScreen.
// If LandingScreen recomposes, the delay shouldn't start again.
LaunchedEffect(true) {
delay(SplashWaitTimeMillis)
currentOnTimeout()
}
/* Landing screen content */
}
값이 들어올 때마다 새로운 값을 remember 된 mutableState에 직접 적용시켜 줍니다.
Composable의 데이터가 바뀔 때 하위 Composable의 remember된 데이터를 바꾸어야 할 경우 사용합니다.
LaunchedEffect 내부의 값을 변경해야하지만 새로운 coroutine을 실행하지 않아야 하거나
effect가 오랜시간 걸릴 때 작업을 유지해야 할 경우rememberUpdatedState를 사용합니다.
@Composable
fun ShowSnackBar(scaffoldState: ScaffoldState = rememberScaffoldState(), timeoutText: String) {
val currentTimeoutText by rememberUpdatedState(timeoutText)
LaunchedEffect(true) {
try {
delay(2000L)
scaffoldState.snackbarHostState.showSnackbar(currentTimeoutText)
} catch (ce: CancellationException) {
}
}
}
위의 경우 timeText가 변하더라도, LaunchedEffect를 재시작하지 않고, 내부 구문을 실행합니다.
만약 rememberUpdatedState를 사용하지 않는다면, timeoutText가 변해도 똑같은 showSnackbar 구문을 표시합니다.
DisposableEffect
LaunchedEffect와 동작이 동일하지만, 정리가 필요한 효과일 때 사용합니다.
키가 변경되거나 Composable이 컴포지션을 종료한 후 정리해야 하는 부수효과에 사용하며,
DisposableEffect 키가 변경되면 컴포저블이 현재 효과를 삭제하고 다시 호출하여 재설정해야 합니다.
https://developer.android.com/jetpack/compose/side-effects?hl=ko#state-effect-use-cases
@Composable
fun HomeScreen(
lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
onStart: () -> Unit,
onStop: () -> Unit
) {
// 새로운 람다식이 들어왔을 때 안전하게 실행할 수 있도록 rememberUpdatedState를 사용
val currentOnStart by rememberUpdatedState(onStart)
val currentOnStop by rememberUpdatedState(onStop)
// lifecycleOwner가 변경되면 DisposableEffect내에 있는 코루틴이 다시 시작
DisposableEffect(lifecycleOwner) {
val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_START) {
currentOnStart()
} else if (event == Lifecycle.Event.ON_STOP) {
currentOnStop()
}
}
lifecycleOwner.lifecycle.addObserver(observer)
// Effect가 Composition을 떠날 때 observer를 삭제
onDispose {
lifecycleOwner.lifecycle.removeObserver(observer)
}
}
/* Home screen content */
}
onDispose 절을 코드 블록의 맨 끝에 적어둬야 하며, 그렇지 않을 시 오류가 발생합니다.
lifecycleOwner가 변경되면, DisposableEffect 내의 코루틴이 다시 시작됩니다.