본문 바로가기

Jetpack Compose

[Jetpack Compose Side-effects] 4. DisposableEffect

DisposableEffect

Activity의 Lifecycle에서 onDestroy가 될 때 observer나 리소스를 정리했던 것처럼 Compose에서는 Composable이 화면에서 사라지면서 observer나 리소스를 정리해야 하는 경우 DisposableEffect를 활용할 수 있습니다.

 

앞서 살펴보았던 LaunchedEffect와 마찬가지로 최초로 화면에 Composable이 그려질 때 DisposableEffectScope이 호출됩니다. 따라서 해당 영역에서 observer나 리소스를 초기화할 수 있습니다. 또한 DisposableEffect 역시 인자로 key를 받게 되는데, LaunchedEffect가 key 값의 변화에 따라 재호출되었던 것처럼 DisposableEffectkey가 변하면 재호출 됩니다. 

 

여기까지만 보면 LaunchedEffect와 별다른 차이가 없어보입니다. 하지만 LaunchedEffect와 다르게 DisposableEffect는 스코프 안에서 반드시 onDispose 함수를 정의해야 합니다. onDispose 함수는 key가 바뀌어서 DisposableEffect 스코프가 재호출 되기 전, 화면에서 Composable UI가 사라질 때 호출됩니다. 따라서 onDispose 함수 안에서는 Composable이 종료될 때 해야 하는 작업들을 정의해놓을 수 있고, 앞서 초기화 했던 observer나 리소스를 해제하거나 제거하는 코드를 작성하는 것이 일반적입니다.

 

구글 공식 문서에서는 onDispose 함수를 비워두는 것을 권장하지 않고 있습니다. 이는 onDispose를 비워두어야 하는 상황이라면 LaunchedEffect로 대체할 수 있기 때문이라고 생각합니다.

 

다음 예시를 보겠습니다. DisposableEffect 스코프가 실행될 때와 onDispose가 일어날 때 ToastMessage로 확인해보는 코드입니다. 버튼을 누르면 DisposableEffect의 key값이 바뀌게 되어 onDispose 호출 후 새롭게 DisposableEffectScope가 실행됩니다. 이 때, ToastMessage가 겹쳐서 onDispose 때 호출되는 ToastMessage가 화면에서는 보이지 않습니다. 앱을 종료해 Composable이 화면에서 보이지 않는 상태가 되면 onDispose가 호출됨을 알 수 있습니다.

@Composable
fun DisposableEffectScreen() {
    Surface(
        modifier = Modifier.fillMaxSize()
    ) {
        val context = LocalContext.current
        var flag by remember { mutableStateOf(false) }

        Column(
            modifier = Modifier.fillMaxSize(),
            verticalArrangement = Arrangement.Center,
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            Button(
                onClick = {
                    flag = !flag
                },
            ) {
                Text("Click")
            }

            DisposableEffect(flag) {
                Toast.makeText(context, "DisposableEffectScope Executed", Toast.LENGTH_SHORT).show()

                onDispose {
                    Toast.makeText(context, "onDispose Executed", Toast.LENGTH_SHORT).show()
                }
            }
        }
    }
}

 

활용

이렇듯 DisposableEffect를 이용하면 Composable의 생성과 종료 시점에서 해야할 작업들을 지정할 수 있기 때문에 Activity의 Lifecycle의 변화에 따른 observer를 Composable에서 설정해야 하는 경우에 유용하게 활용할 수 있습니다. 

 

lifecycleOwner를 DisposableEffect의 key로 설정해 Activity의 Lifecycle이 변할 때마다 DisposableEffect 스코프가 재실행되면서 Lifecycle에 맞는 코드를 호출하게 됩니다. 또한 Composable이 종료되면 onDispose가 호출되어 observer를 제거해줍니다.

 

@Composable
fun DisposableEffectScreen(
    lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
    onStart: () -> Unit = {},
    onStop: () -> Unit = {}
) {
    Surface(
        modifier = Modifier.fillMaxSize()
    ) {
        val currentOnStart by rememberUpdatedState(newValue = onStart)
        val currentOnStop by rememberUpdatedState(newValue = onStop)
        

        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)

            onDispose {
                lifecycleOwner.lifecycle.removeObserver(observer)
            }
        }
    }
}

 

위와 같이 Activity의 Lifecycle을 Composable에 가져와 사용할 수 있지만, 구글에서는 Lifecycle과 관련된 더욱 간단하고 강력한 Side-effects API인 LifecycleEventEffect를 제공합니다.