[Android] Compose SideEffect의 종류와 역할

Ham's Velog·2024년 7월 1일
post-thumbnail

SideEffect(부수효과)란?

Android Jetpack Compose에서 Side Effect는 UI 상태가 변경될 때 발생할 수 있는 부수 효과를 안전하게 처리할 수 있도록 돕는 기능입니다.
Compose에서 부수 효과는 Composable 함수 외부의 상태나 데이터를 변경하거나 외부에서 제공된 데이터를 컴포저블 내부로 가져오는 작업을 의미합니다.
Compose에서는 부수효과를 처리하기 위해 제공되는 3가지의 Effect API와 다양한 State 함수가 함께 제공되고 있습니다.

  • LaunchedEffect
  • DisposableEffect
  • SideEffect
  • rememberCoroutineScope
  • rememberUpdatedState
  • produceState
  • derivedStateOf
  • snapshotFlow

이번 포스팅에서는 3가지의 Effect API에 대한 역할을 알아보고 어떤 상황일 때 사용할 수 있는지 알아볼 예정입니다.

LaunchedEffect

@Composable
fun LaunchedEffect(
	key1 : Any?,
	block : suspend CorountineScope.() -> Unit
)

LaunchedEffect는 Composable 함수 범위 내에서 suspend 함수를 실행하는 특징을 가지고 있습니다.
이밖에도 다음과 같은 특징들이 있습니다.

  • 비동기 작업을 지원하는 Effect API
  • Composable 함수가 최초 Composition이 되었을 때 부수효과 실행
  • 파라미터로 지정된 Key의 값이 변경되었을 때 부수효과 실행
  • 지정할 수 있는 Key의 갯수는 제한없음

다음은 LaunchedEffect를 활용한 코드 입니다.

@Composable
fun DetectRoute(
    viewModel: DetectViewModel = hiltViewModel(),
    imageUri: Uri,
    onNextStep: () -> Unit,
    onPreviousStep: () -> Unit
) {
    val state by viewModel.uiState.collectAsStateWithLifecycle()

    LaunchedEffect(Unit) {
        viewModel.sideEffect.collect { effect ->
            when (effect) {
                is DetectSideEffect.ResizedImage -> { /*이미지 리사이징을 실행하는 함수*/ }
                is DetectSideEffect.NavigateToMosaic -> { onNextStep() }
                is DetectSideEffect.NavigateToHome -> { onPreviousStep() }
            }
        }
    }
    
    ...
}

해당 코드는 ViewModel에 정의되어있는 SharedFlow 타입의 sideEffect를 LaunchedEffect 블럭 내부에서 수집하고 있는 모습입니다.
DetectSideEffect 클래스에서 정의된 부수효과들을 Composable 함수 내에서 처리해주고 있습니다.

DisposableEffect

@Composable
fun DisposableEffect(
	key1 : Any?,
	effect : DisposableEffectScope.() -> DisposableEffectResult
) {
	remember(key1) { DisposableEffectImpl(effect) }
}

DisposableEffect는 Composition이 완료된 후 실행되는 부수효과 입니다.
또한 Composition이 종료될 때 DisposableEffect 내부에서 제공하는 onDispose 블럭 내부에서 정리 작업을 수행할 수 있습니다.

  • Composition이 완료 되면 실행되는 부수효과
  • Composition이 종료 될 때 onDispose를 통해 정리 작업 수행 가능
  • 파라미터로 지정된 Key의 값이 변경되었을 때 부수효과 실행
  • 지정할 수 있는 Key의 갯수는 제한없음

다음은 DisposableEffect를 활용한 코드 입니다.

@Composable
fun HomeScreen(
    lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
    onStart: () -> Unit,
    onStop: () -> Unit
) {
    val currentOnStart by rememberUpdatedState(onStart)
    val currentOnStop by rememberUpdatedState(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)
        }
    }
    
    ...
}

*위 의 예시는 공식문서에 등록된 예시 코드입니다.

LifecycleOwner의 생명주기 상태를 관찰하여 ON_STARTON_STOP 이벤트가 발생할 때 지정된 콜백 함수를 호출합니다
그리고 Observer를 lifecycleOwner의 생명주기에 등록합니다.
마지막으로 onDispose 블럭 내부에 Observer를 제거해주는 모습을 볼 수 있습니다.

SideEffect

@Composable
@NonRestartableComposable
@ExplicitGroupsComposable
@OptIn(InternalComposeApi::class)
fun SideEffect(
    effect: () -> Unit
) {
    currentComposer.recordSideEffect(effect)
}

SideEffect도 마찬가지로 Composition이 완료된 후에 실행되는 부수효과입니다.
또한 Composable의 상태가 변경될 때마다(Recomposition) 실행되며 Composable의 내부 상태를 외부 시스템과 동기화하거나 컴포지션이 완료된 후에 매번 실행되어야 하는 작업을 수행하는 데 사용됩니다.

  • Composition이 완료되면 실행되는 부수효과
  • Recomposition이 발생될 때마다 실행
  • Composable 내부의 상태를 외부 시스템과 동기화하거나 Composition이 완료된 후에 실행되어야 하는 작업을 정의

다음은 SideEffect 활용한 코드 입니다.

@Composable
fun rememberFirebaseAnalytics(user: User): FirebaseAnalytics {
    val analytics: FirebaseAnalytics = remember {
        FirebaseAnalytics()
    }
    
    SideEffect {
        analytics.setUserProperty("userType", user.userType)
    }
    return analytics
}

*위 의 예시는 공식문서에 등록된 예시 코드입니다.

해당 코드는 FirebaseAnalytics를 활용하여 Composable의 상태가 변경될 때마다 사용자 속성을 업데이트하는 방법입니다.
이를 통해 Firebase Analytics에서 사용자의 타입 정보를 추적하여 모니터링을 가능하게 해줍니다.

해당 코드를 활용한다면 다음과 같이 활용할 수 있습니다.

@Composable
fun Test(user: User) {
    val analytics = rememberFirebaseAnalytics(user)
    
    Button(onClick = {
        analytics.logEvent("button_click", null)
    }) {
        Text("Click me")
    }
}

버튼을 눌렀을 때 FirebaseAnalytics의 이벤트 로그를 수집할 수 있습니다.

profile
#안드로이드개발자

0개의 댓글