티스토리 뷰

728x90

 

Side Effect란?

컴포즈에서 Side Effect는 컴포저블 함수의 밖에서 발생하는 앱 상태에 대한 변경사항이다.

예를 들어 사용자가 버튼을 클릭하면 다른 화면이 열리거나, 앱이 인터넷이 연결되어 있지 않을 때의 메시지 등이 될 수 있다.

 

기본적으로 컴포저블 함수는 Side Effect에 자유로워야 한다. 

이때 자유로워야 한다는 말은, 컴포저블 함수를 트리거하기 위해서는 파라미터를 변경하거나 컴포저블을 재호출해야 한다는 뜻이다.

컴포저블 내부에서 컴포저블 외부에 있는 변수나 동작에 영향을 주도록 설계해서는 안 된다.

하나의 컴포저블에 대해 리컴포지션이 여러번 발생할 수 있고, 여러 스레드에서 호출될 수 있기 때문이다.

 

Side Effect를 이해하고 잘 활용하기 위해, 관련된 컴포저블 함수를 알아보려고 한다!

Side Effect를 처리하기 위해 아래와 같은 함수들이 있다.

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

이번 포스팅에서는 LaunchedEffect, DisposableEffect, SideEffect를 각각 알아보고, 비교해 보려고 한다.

나머지 함수들은 다음 포스팅에......!

 

 

LaunchedEffect

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

먼저 컴포즈를 개발하며 가장 많이 사용하는 LaunchedEffect이다.

LaunchedEffect는 컴포저블 안에서 안전하게 suspend 함수를 호출하기 위해 사용하는 컴포저블 함수다.

 

LaunchedEffect의 key

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

LaunchedEffect의 매개변수로 key를 전달할 수 있다.

key가 변경되면 LaunchedEffect의 내부 블록이 다시 호출된다.

 

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

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

@Composable
fun LaunchedEffect(
    vararg keys: Any?,
    block: suspend CoroutineScope.() -> Unit
)

여러 key 매개변수를 가지는 함수들이 오버로딩되어 있기 때문에, 개수 제한 없이 여러 key를 지정할 수 있다.

여러 key 중 하나라도 변경이 일어나면, LaunchedEffect의 내부 블록이 다시 호출된다.

두 개 이상의 값이 변경될 때 하나의 로직을 수행하고 싶을 때 사용할 수 있다.

 

상수를 key로 사용하기

LaunchedEffect(Unit) {
    viewModel.fetchMeetings()
}

 

 

컴포저블의 수명 주기 동안 한 번만 실행시켜야 하는 로직인 경우, Unit, 1, false처럼 변하지 않는 상수를 key로 사용한다.

예를 들어 ViewModel에서 데이터를 가져오는 함수를 한 번만 호출하기 위해, LaunchedEffect(Unit) { ... }를 사용할 수 있다.

 

configuration change에도 컴포저블은 리컴포지션되기 때문에, LaunchedEffect(Unit) 내부 블록이 실행된다.

즉, Activity의 onCreate()에서 함수를 호출하는 것과 유사하다.

configuration change마다 호출하지 않으려면 이러한 함수 호출을 ViewModel 내부에서 하는 것이 좋다.

 

LaunchedEffect의 코루틴 생명주기

LaunchedEffect는 suspend 함수를 호출할 수 있는 함수다.

그렇다면 suspend 함수가 호출되는 코루틴의 생명주기는 어떻게 될까?

 

먼저 컴포저블이 컴포지션을 시작하면 현재 컴포지션의 CoroutineContext에서 코루틴이 실행된다.

컴포저블이 컴포지션을 종료하면 코루틴이 취소된다.

또한 LaunchedEffect의 key가 변경되면, 기존 코루틴이 취소되고 새 코루틴이 실행된다.

 

 

DisposableEffect

@Composable
fun DisposableEffect(
    key1: Any?,
    effect: DisposableEffectScope.() -> DisposableEffectResult
): Unit

DisposableEffect는 key가 변경되면 내부 블록이 다시 실행된다는 점에서 LaunchedEffect와 유사하다.

그런데 DisposableEffect는 컴포저블이 컴포지션을 종료했을 때 자원을 정리해야 하는 경우 사용한다.

 

DisposableEffect의 onDispose

class DisposableEffectScope {
    inline fun onDispose(
        crossinline onDisposeEffect: () -> Unit
    ): DisposableEffectResult = object : DisposableEffectResult {
        override fun dispose() {
            onDisposeEffect()
        }
    }
}

DisposableEffect의 effect 블록은 DisposableEffectScope를 리시버로 가지고 있다.

DisposableEffectScope는 유일한 함수로 onDispose를 가지고 있는데, 이 함수는 컴포저블이 컴포지션을 종료했을 때 실행된다.

즉, DisposableEffect의 내부 블록에서 onDispose를 호출하여 컴포저블이 종료됐을 때의 로직을 지정해줄 수 있다.

 

onDispose 사용 예시

DisposableEffect(lifecycleOwner) {
    val observer =
        LifecycleEventObserver { _, event ->
            // ...
        }
    lifecycleOwner.lifecycle.addObserver(observer)
    onDispose {
        lifecycleOwner.lifecycle.removeObserver(observer)
    }
}

예를 들어 LifecycleEventObserver와 함께 DisposableEffect를 사용할 수 있다.

lifecycle이 변경될 때마다 실행할 로직을 작성하고, lifecycleOwner에 observer를 추가한다.

그리고 onDispose 내부에서 removeObserver를 호출하여 컴포저블의 컴포지션이 종료되었을 때 Observer를 제거한다.

onDispose에서 removeObserver를 호출하지 않으면, observer가 lifecycleOwner에 계속 남아있게 되어 메모리 누수나 예상치 못한 버그가 발생할 수 있다.

 

 

SideEffect

@Composable
fun SideEffect(
    effect: () -> Unit
)

SideEffect는 컴포저블 내부의 상태를 컴포저블 외부와 공유하기 위해 사용하는 컴포저블 함수다.

SideEffect의 내부 블록 effect는 리컴포지션이 완료될 때마다 실행된다.

LaunchedEffect와 다르게 내부 블록이 suspend 함수가 아니고, UI 스레드에서 동기적으로 실행된다.

 

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

Compose의 State와 관련 없는 작업을 수행할 때 주로 사용된다.

예를 들어 로깅이나 통계, 외부 객체 업데이트 등이 될 수 있다.

 

 

LaunchedEffect vs DisposableEffect vs SideEffect

 

LaunchedEffect

  • 특정 시점에만 실행되어야 하는 경우 사용한다.
  • key가 변경될 때마다 실행된다.

DisposableEffect

  • 자원 해제가 필요한 경우 사용한다.
  • key가 변경될 때마다 실행된다.
  • onDispose 함수로 자원을 해제할 수 있다.

SideEffect

  • 리컴포지션이 완료될 때마다 실행되어야 하는 경우 사용한다.

 

728x90
250x250
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2025/06   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
글 보관함