안드로이드

[Jetpack Compose] Side Effect ( 1 / 2 )

Bum_2 2024. 12. 1. 15:10

컴포저블의 범위 밖에서 발생하는 앱 상태 변경을 다루기 위한 도구로서 SideEffect를 사용한다. 공식 문서에 따르면, 컴포저블은 이상적으로 Side Effect가 없어야 한다고 한다. 하지만 앱을 설계하면서 UI의 상태 외에도 결국 UI와 긴밀하게 연결되어야하는 부분이 있다. 이를 지원해주는 것이 SideEffect 인데, 어떻게 동작하고 활용되는지 알아보자.

 

Side Effect

Compose function 외부에서 발생하는 앱 상태의 변화이다. 

 

위의 설명은 공식 문서에 나와있는 선언형 UI 철학에 어긋날 수 있는 작업을 처리하기 위한 설명이다. 이를 풀어보자면 다음과 같이 말할 수 있다. Compose 내부 상태 변화가 외부 시스템에 영향을 미치거나, 외부 상태 변화를 UI에 반영해야 할 때 사용하는 도구이다.

 

Composable은 기본적으로 바깥쪽에서 안쪽의 Composable 방향으로 즉, 단방향 데이터 흐름으로 State를 전달한다. 여기서 Composable은 순수 함수 처럼, 외부의 전역 변수나 시스템에 직접적인 영향을 주지 않아야 한다. 하지만 외부 시스템과 상호작용해야 하는 경우(Side Effect)는 Effect API(SideEffect, LaunchedEffect, DisposableEffect)를 사용하여 안전하게 처리한다.

 

위의 설명은 Side Effect의 큰 개념으로서의 설명이었고, 그렇다면 JetPack Compose에서 사용하는 SideEffect, LaunchedEffect, DisposableEffect에 대한 특징, 차이점과 사용법에 대해 살펴보자.


SideEffect

Jetpack Compose에서 컴포저블 함수 내부의 상태를 Compose 외부의 시스템과 동기화하기 위해 사용되는 컴포저블 함수이다.

코드

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

 

특징

  • 리컴포지션 후 실행: SideEffect는 컴포저블이 성공적으로 리컴포지션될 때마다 실행된다.
  • 동기적 실행: SideEffect 블록 내의 코드는 동기적으로 실행되며, 비동기 작업을 처리할 때는 다른 Effect API(LaunchedEffect 등)를 사용하는 것이 적합하다.

SideEffect는 컴포지션 될 때 한 번, 그 이후 리컴포지션이 발생한 이후 무조건 실행되기 때문에, Composable에서 발생한 데이터를 ViewModel에 업데이트하거나 외부 상태를 업데이트할 때 사용한다. 


LaunchedEffect

Jetpack Compose에서 제공하는 컴포저블(Composable) 함수로, 컴포넌트가 처음 시작되거나 특정 키 값이 변경될 때 코루틴을 사용해 비동기 작업을 실행하는 데 사용된다.

@Composable
@NonRestartableComposable
@OptIn(InternalComposeApi::class)
fun LaunchedEffect(
    key1: Any?,
    block: suspend CoroutineScope.() -> Unit
) {
    val applyContext = currentComposer.applyCoroutineContext
    remember(key1) { LaunchedEffectImpl(applyContext, block) }
}

 

특징

  1. Lifecycle-aware:
    • LaunchedEffect는 컴포저블의 생명 주기를 인지하며, 컴포저블이 리컴포지션될 때 이미 실행 중인 작업을 관리한다.
    • 컴포저블이 제거되면, 자동으로 코루틴을 취소.
  2. Key 기반 재실행:
    • LaunchedEffect는 전달받은 key(또는 여러 키)가 변경될 때마다 다시 실행된다.
    • key가 변경되지 않으면 기존 작업은 유지.
  3. 비동기 작업:
    • 내부에서 Suspend 함수와 같은 비동기 작업을 사용할 수 있다.

DisposableEffect

Jetpack Compose에서 컴포저블의 생명 주기(Lifecycle)를 인식하고, 필요한 초기화 작업과 정리 작업을 수행하기 위해 제공되는 컴포저블 함수이다.

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

 

주요 특징

  1. 생명 주기 관리:
    • DisposableEffect는 컴포저블이 화면에 나타날 때 실행되며, 컴포저블이 화면에서 제거될 때 정리 작업을 수행한다.
    • 이는 Compose 내부에서 발생하는 상태 변화컴포저블의 수명을 동기화하는 데 사용됨.
  2. 정리 작업(onDispose):
    • DisposableEffect는 onDispose 블록을 통해 리소스를 해제하거나 정리할 수 있다.
    • 예: 센서 관리, 데이터 스트림 종료, 외부 리소스 해제 등.
  3. 키(Key)를 기반으로 동작:
    • DisposableEffect는 전달된 키가 변경될 때, 이전에 실행된 작업을 정리하고 새로운 작업을 실행한다.

SideEffect와 LaunchedEffect는 Unit을 반환하는 간단한 람다를 사용하지만, DisposableEffect의 effect 파라미터는   DisposableEffectScope.() -> DisposableEffectResult 타입이다. 즉, Unit이 아닌 DisposableEffectResult을 반환해야 하며, 해당 인터페이스는 아래와 같이 되어있어서 dispose를 구현해야 한다. 또한, 코틀린의 후행 람다 문법 지원으로 인해 effect 파라미터를 가장 나중에 받기 때문에 dispose 구현체는 최하단에 작성해야 한다.

 

interface DisposableEffectResult {
    fun dispose()
}

 

 

DisposableEffect는 컴포지션 될 때 1번 영역을 호출하며 Key값이 변경될 때마다 onDispose 블록(2번)이 호출되고 1번 영역이 후행으로 실행된다.

DisposableEffect(key1 = value) {
    // 1번

    onDispose {
        // 2번
    }
}

 

 

 

- Reference

 

Jetpack Compose Side Effect - Mash-up

 

Jetpack Compose Doc 읽기 — Part1[기초]

 

[Compose] Side Effect 관련 API 재 정리

 

Side-effects in Compose