[Android] Compose 부수효과 정리

이동건·2023년 11월 22일
1

android

목록 보기
2/3
post-thumbnail

Compose를 사용하면서 LaunchedEffect, DisposableEffect와 같은 일명 부수 효과 함수들을 사용할 일들이 꽤 많다. 이에 대해 정리의 필요성을 느끼고, 공식문서를 읽고 학습한 내용을 바탕으로 정리하였다.

부수효과 Composable 함수의 범위 밖에서 발생하는 앱 상태에 관한 변경사항이다.

부수 효과는 되도록 없으면 좋으나, 컴포저블의 범위 밖에서 상태의 변경이 필요한 상황이면 예측 가능한 방식으로 실행되도록 Effect API를 사용해야 한다.

Effect API에서의 작업은 UI와 관련되어 있어야 하며, Compose의 기본적인 단방향 흐름을 해치면 안된다.

기본적으로 반응형 UI는 비동기로 진행되며 Compose에서는 이를 콜백을 사용하지 않고 코루틴을 이용한다.


LaunchedEffect

LaunchedEffect가 컴포지션에 진입하면, 컴포지션이 이루어지거나 Composable 함수가 최초로 호출될 때, 컴포지션의 CoroutineContext로 코루틴 블락을 시작한다.

만약 LaunchedEffect 함수의 인자인 key 값이 변경되면, 현재 실행중인 코루틴은 취소되고 재시작된다. 해당 Effect가 컴포지션을 벗어나면 코루틴은 자동 취소된다. 키 값의 변화에 따라 중단 함수를 호출하거나 컴포저블 내부에서 외부의 값을 구독하면서 중단 함수를 호출해야 하는 경우에는 LaunchedEffect를 사용한다.

내부적으로 remember(key1) 블락을 사용중이며, 이는 다음과 같이 동작한다.

  1. 키 값이 기존과 같으면, 기존 calculation 구문의 결과를 기억하고 해당 값을 리턴한다.
  2. 키 값이 기존과 달라지면, calculation 구문의 결과 값을 새롭게 생성하고 해당 값을 기억한다.

LaunchedEffect에 키 값을 여러 개 지정할 수 있으며, 여러 개 값들 중 하나라도 변경되면 코루틴이 재실행된다.

LaunchedEffect(key){
   progress = 0f
   while (progress < 1f){
      progress += 1f / seconds
      delay(1000)     // 중단 함수

      if (progress >= 1f) onProgressCompleted()
   }
}

LaunchedEffect(Unit){
   viewModel.moveToSetting.collectLatest   // 중단 함수 {
       popBackStack(it)
   }
}

첫번째 코드 블락은 키 값이 변함에 따라 while 문 내부에서 실행 중인 코루틴이 취소되고, 이에 따라 코루틴이 재시작되며 progress값은 0으로 할당된다

두번째 코드 블락은 키 값으로 Unit을 설정하였다. 이렇게 변하지 않는 값을 할당하면, 코루틴의 수명주기는 해당 컴포저블의 수명주기와 동일하게 된다. 이런 방식은 ViewModel에서 특정 값의 변화를 구독(collectLatest)하는 경우 유용하다.


DisposableEffect

DisposableEffect는 컴포저블이 컴포지션을 종료한 후 정리 해야 하는 부수 효과가 있는 경우 사용한다. DisposableEffect에 Key값을 설정하면, 해당 키 값이 변경되면 내부의 onDispose 절이 실행되고, 해당 Effect를 다시 호출하여 재설정한다.

해당 Effect는 컴포저블의 수명주기에 맞게 정리되어야 할 리스너가 있거나 처리해야 할 이벤트가 있는 경우 사용한다.

@Composable
fun DisposableEffectWithLifeCycle(
    onResume: () -> Unit,
    onPause: () -> Unit,
    lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current
) {
    // Safely update the current lambdas when a new one is provided
    val currentOnResume by rememberUpdatedState(onResume)
    val currentOnPause by rememberUpdatedState(onPause)

    // If `lifecycleOwner` changes, dispose and reset the effect
    DisposableEffect(lifecycleOwner) {
        // Create an observer that triggers our remembered callbacks
        // for lifecycle events
        val observer = LifecycleEventObserver { _, event ->
            when (event) {
                Lifecycle.Event.ON_RESUME -> {
                    currentOnResume()
                }
                Lifecycle.Event.ON_PAUSE -> {
                    currentOnPause()
                }
                else -> {}
            }
        }

        // Add the observer to the lifecycle
        lifecycleOwner.lifecycle.addObserver(observer)

        // When the effect leaves the Composition, remove the observer
        onDispose {
            lifecycleOwner.lifecycle.removeObserver(observer)

        }
    }
}

DisposableEffect는 onDispose 절을 코드 블록의 최종 문장으로 포함해야 한다. 그렇지 않으면 빌드 오류가 표시된다.


SideEffect

SideEffect 는 Compose의 상태를 Compose에서 관리하지 않는 객체와 공유하려면 해당 Effect를 사용한다. 이는 리컴포지션 성공마다 호출된다.

@Composable
fun rememberAnalytics(user: User): FirebaseAnalytics {
    val analytics: FirebaseAnalytics = remember {
        /* ... */
    }

    // On every successful composition, update FirebaseAnalytics with
    // the userType from the current User, ensuring that future analytics
    // events have this metadata attached
    SideEffect {
        analytics.setUserProperty("userType", user.userType)
    }
    return analytics
}

FirebaseAnalytics 라이브러리를 사용하여 유저의 타입을 설정하는 경우(Compose 에서 관리하지 않는 객체와 공유) 해당 효과를 사용하여 처리할 수 있다.


Effect API 사용시 주의 사항

코루틴의 수명주기

LaunchedEffect와 같이 컴포저블 내에서 중단 함수를 사용하는 경우, 코루틴 스코프를 정의하고 launch 구문을 통해 중단 함수를 호출하게 된다.

코루틴은 구조화된 동시성이라는 특징을 가지고 있으므로, 그 수명주기는 CoroutineScope로 제한되어 있다. 이 말은 코루틴이 그 수명주기가 종료되면 취소되어야 한다는 것을 의미한다. 따라서 컴포저블 내에서 사용하는 코루틴의 수명주기는 컴포저블의 수명주기와 일치해야 한다. 즉, 컴포저블이 컴포지션을 벗어나는 순간, 해당 코루틴은 자동으로 취소되어야 한다.

만약 Composable 내부에서 Activity의 lifecycleScope를 사용한다면, 해당 스코프로 지정된 코루틴은 컴포지션이 종료되어도 취소되지 않는다.

Compose에서는 rememberCoroutineScope 라는 api를 제공하고 있으며, 이 CoroutineScope는 Composable의 생명주기를 따른다.

컴포저블이 파괴될 때 자동으로 취소되어야 하는 코루틴을 생성해야 한다면, rememberCoroutineScope를 사용하자.

참고

https://developer.android.com/jetpack/compose/side-effects?hl=ko

https://kotlinworld.com/245

profile
성장하는 활동적인 개발자

0개의 댓글