본문 바로가기

Jetpack Compose

[Jetpack Compose Side-effects] 5. LifecycleEventEffect

서론

구현 요구사항 중 앱이 백그라운드에서 포그라운드 상태로 진입하는 경우를 판단해야 하는 사항이 있었습니다. 리서치 결과 Activity의 Lifecycle의 변화에 따라 원하는 작업을 수행하고자 했습니다. 따라서 Activity의 Lifecycle과 Composable을 연결해야 했고, 앞서 포스팅한 DisposableEffect를 이용하는 것이 보편적인 방법인 듯 했습니다. 하지만 스택오버플로우 답변을 보고 새로운 Side-effects를 이용해 보다 간단하게 문제를 해결할 수 있음을 알게 되었습니다.

LifecycleEventEffect

먼저 해당 Side-effects를 사용하기 위해서는 다음과 같이 lifecycle-runtime-compose:2.7.0-alpha01 이상을 implementation 해야 합니다. 리서치 자료가 많지 않은 걸로 보아서 아직 stable 버전이 아니라 그런 것 같습니다.

 

implementation("androidx.lifecycle:lifecycle-runtime-compose:2.7.0-beta01")

 

 

LifecycleEventEffect는 함수 이름대로 현재 Composable과 연결된 Activity의 Lifecycle을 감지해 특정 Lifecycle 시점에 원하는 작업을 할 수 있도록 도와주는 함수 입니다. 기존 DisposableEffect를 이용해 Lifecycle을 감지하는 방법을 알고 있다면 다음의 LifecycleEventEffect 구현부를 쉽게 이해할 수 있습니다.

 

@Composable
fun LifecycleEventEffect(
    event: Lifecycle.Event,
    lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
    onEvent: () -> Unit
) {
    if (event == Lifecycle.Event.ON_DESTROY) {
        throw IllegalArgumentException("LifecycleEventEffect cannot be used to " +
            "listen for Lifecycle.Event.ON_DESTROY, since Compose disposes of the " +
            "composition before ON_DESTROY observers are invoked.")
    }

    // Safely update the current `onEvent` lambda when a new one is provided
    val currentOnEvent by rememberUpdatedState(onEvent)
    DisposableEffect(lifecycleOwner) {
        val observer = LifecycleEventObserver { _, e ->
            if (e == event) {
                currentOnEvent()
            }
        }

        lifecycleOwner.lifecycle.addObserver(observer)

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

 

내부적으로 DisposableEffect를 이용하고 있고 DisposableEffect에서 LifecycleEventObserver를 등록하고 onDispose에서 이를 해제하고 있는 모습입니다. 따라서 LifecycleEventEffect는 기존의 DisposableEffect를 사용하기 편리하도록 감싼 함수라고 생각하면 됩니다. 

 

LifecycleEventEffect는 event와 lifecycleOwner를 매개변수로 가지는데 event는 Lifecycle.Event 타입으로, 이는 enum class로 구현되어 있고 ON_CREATE, ON_START, ON_RESUME 등과 같이 Activity의 Lifecycle의 상태들에 해당하는 값들의 집합입니다. 따라서 event에 Lifecycle.Event.ON_RESUME을 넘겨주면 Activity가 onResume 될 때 수행해야 하는 이벤트를 LifecycleEventEffect 스코프에서 수행합니다.

 

주의해야할 점은 Lifecycle.Event.ON_DESTROY를 인자로 넘겨주면 IllegalArgumentException이 발생합니다. 이는 

Activity의 ON_DESTROY 상태가 감지되기 전에 Composable이 dispose 되기 때문입니다.

 

또 다른 매개변수인 lifecycleOwner는 디폴트로 LocalLifecycleOwner.current를 가지므로 아무런 인자를 넘겨주지 않는다면 기본적으로 현재 Composable과 연결된 Activity의 Lifecycle을 가져옵니다.

 

LifecycleEventEffect를 이용해 Activity의 Lifecycle을 로그로 출력해보았습니다.

 

@Composable
fun LifecycleEventEffectScreen() {
    Surface(
        modifier = Modifier.fillMaxSize()
    ) {
        val tag = "Lifecycle"

        LifecycleEventEffect(event = Lifecycle.Event.ON_START) {
            Timber.tag(tag).d("onStart")
        }

        LifecycleEventEffect(event = Lifecycle.Event.ON_CREATE) {
            Timber.tag(tag).d("onCreate")
        }

        LifecycleEventEffect(event = Lifecycle.Event.ON_RESUME) {
            Timber.tag(tag).d("onResume")
        }

        LifecycleEventEffect(event = Lifecycle.Event.ON_PAUSE) {
            Timber.tag(tag).d("onPause")
        }

        LifecycleEventEffect(event = Lifecycle.Event.ON_STOP) {
            Timber.tag(tag).d("onStop")
        }
    }
}

 

2023-11-12 22:57:05.363  3403-3403  Lifecycle               com.peter.baekmooneibulyeoilta       D  onStart
2023-11-12 22:57:05.364  3403-3403  Lifecycle               com.peter.baekmooneibulyeoilta       D  onCreate
2023-11-12 22:57:05.364  3403-3403  Lifecycle               com.peter.baekmooneibulyeoilta       D  onResume
2023-11-12 22:59:44.778  3403-3403  Lifecycle               com.peter.baekmooneibulyeoilta       D  onPause    2023-11-12 22:59:44.816  3403-3403  Lifecycle               com.peter.baekmooneibulyeoilta       D  onStop

 

앱을 실행시키니 우리가 알고 있는 Activity의 Lifecycle 순서대로 onStart -> onCreate -> onResume 순서로 로그가 출력됨을 확인할 수 있었습니다. 앱을 백그라운드로 보내니 onPause -> onStop이 출력됨을 확인할 수 있었습니다.

 

LifecycleStartEffect / LifecycleResumeEffect

LifecycleEventEffect 외에도 LifecycleStartEffect와 LifecycleResumeEffect가 존재하는데, DisposableEffect에 Activity Lifecycle을 감지하는 기능이 추가되었다고 생각하면 됩니다. LifecycleResumeEffect를 예로 들면, LifecycleResumeEffect는 key를 매개변수로 받고 onPauseOrDispose 스코프를 반드시 가져야 합니다. 

 

LifecycleResumeEffect라는 이름에 걸맞게 Composable과 연관된 Activity의 Lifecycle이 onResume 이 될 때 첫 실행이 되고, key가 변하면 onPauseOrDispose 스코프가 호출되고 다시 LifecycleResumeEffect 스코프가 호출됩니다. onPause 상태가 되거나 컴포저블이 화면에서 사라지면 onPauseOrDispose 스코프가 호출되고 onPause 상태에서 다시 onResume 상태가 되면 LifecycleResumeEffect 스코프가 호출됩니다.

 

LifecycleStartEffect의 경우 onStopOrDispose를 구현해야 하고 Activity의 Lifecycle이 onStart 일 때 스코프가 실행됩니다.

 

다음 예시 코드를 보면 앱이 실행되면서 Activity가 onResume 상태로 진입해서 LifecycleResumeEffect 스코프가 실행되고 버튼을 클릭하면 key가 변경되므로 onPauseOrDispose가 호출된 후 다시 LifecycleResumeEffect 스코프가 호출됩니다. 

 

@Composable
fun SideEffectScreen() {
    Surface(
        modifier = Modifier.fillMaxSize()
    ) {
        val tag = "Lifecycle"
        var value by remember { mutableStateOf(false) }

        LifecycleResumeEffect(key1 = value) {
            Timber.tag(tag).d("LifecycleResumeEffect Executed")
            onPauseOrDispose {
                Timber.tag(tag).d("onPauseOrDispose Executed")
            }
        }

        Column(
            modifier = Modifier.fillMaxSize(),
            horizontalAlignment = Alignment.CenterHorizontally,
            verticalArrangement = Arrangement.Center
        ) {
            Button(
                modifier = Modifier.wrapContentSize(),
                onClick = { value = !value }
            ) {
                Text("Change key!")
            }
        }
    }
}

 

2023-11-13 23:31:35.419 6875-6875 Lifecycle com.peter.baekmooneibulyeoilta D LifecycleResumeEffect Executed (처음 앱을 실행했을 때)
2023-11-13 23:31:37.765 6875-6875 Lifecycle com.peter.baekmooneibulyeoilta D onPauseOrDispose Executed (버튼을 클릭했을 때) 2023-11-13 23:31:37.765 6875-6875 Lifecycle com.peter.baekmooneibulyeoilta D LifecycleResumeEffect Executed (버튼클릭했을 때)
2023-11-13 23:31:39.497 6875-6875 Lifecycle com.peter.baekmooneibulyeoilta D onPauseOrDispose Executed (앱이 onPause 상태에 진입할 때)
2023-11-13 23:31:39.497 6875-6875 Lifecycle com.peter.baekmooneibulyeoilta D LifecycleResumeEffect Executed (앱이 다시 onResume 상태로 돌아왔을 때)