본문 바로가기

안드로이드 개발/Compose

Compose에서 사이드 이펙트 관리하기

반응형

Jetpack Compose에서의 사이드 이펙트란?

Jetpack Compose에서 _사이드 이펙트_는 컴포저블 함수 외부의 상태를 변경하거나, 네트워크 요청, 데이터 저장, 토스트 메시지 표시 등 UI 외부와 상호작용하는 작업을 의미합니다. 이러한 작업은 컴포저블의 재구성(recomposition) 시 의도치 않게 여러 번 실행될 수 있으므로, Compose가 제공하는 사이드 이펙트 API를 통해 안전하게 관리해야 합니다^3^8.


주요 사이드 이펙트 API와 예제

1. LaunchedEffect

  • 특정 키 값이 변경될 때만 코루틴을 실행하며, 주로 suspend 함수를 호출할 때 사용합니다^2^5.
  • 키 값이 바뀌면 이전 코루틴은 자동으로 취소되고 새로운 코루틴이 실행됩니다.

예제 1: 최초 1회 데이터 로딩

@Composable
fun LaunchedEffectTest() {
    val data = remember { mutableStateOf("로딩 중...") }

    LaunchedEffect(Unit) {
        data.value = loadData()
    }

    Text(text = data.value)
}

suspend fun loadData(): String {
    delay(2000L)
    return "데이터 로딩 완료"
}

이 코드는 컴포저블이 처음 그려질 때 한 번만 데이터 로딩 작업을 수행합니다^2.


예제 2: 입력값이 바뀔 때마다 스낵바 표시

@Composable
fun KotlinWorldScreen() {
    val scaffoldState = rememberScaffoldState()
    var text by rememberSaveable { mutableStateOf("") }

    LaunchedEffect(text) {
        scaffoldState.snackbarHostState.showSnackbar(
            message = "Current Text is $text"
        )
    }

    Scaffold(scaffoldState = scaffoldState) {
        Column(modifier = Modifier.padding(16.dp)) {
            OutlinedTextField(
                value = text,
                onValueChange = { text = it }
            )
        }
    }
}

이 코드는 TextField의 입력값이 변할 때마다 LaunchedEffect가 재실행되어 스낵바를 띄웁니다^5.


2. 여러 키 값 사용

LaunchedEffect는 여러 개의 키 값을 받을 수 있으며, 이 중 하나라도 바뀌면 코루틴이 재실행됩니다.

val key = remember { UUID.randomUUID().toString() }
LaunchedEffect(key, isLoading.value) {
    // 부수 효과 실행
}

여기서 key나 isLoading.value가 변경되면 LaunchedEffect 내의 코드가 다시 실행됩니다^1.


3. 기타 사이드 이펙트 API

  • DisposableEffect: 컴포저블이 활성화될 때 특정 작업을 수행하고, 비활성화될 때 정리 작업을 할 수 있습니다.
  • SideEffect: 컴포저블이 성공적으로 재구성된 후 실행되어야 하는 작업에 사용됩니다.
  • rememberCoroutineScope: 컴포저블 내에서 코루틴을 실행할 수 있는 스코프를 제공합니다^7.

1. DisposableEffect

컴포저블의 생명주기에 맞춰 리소스 정리를 필요로 하는 작업에 사용됩니다. onDispose 블록에서 정리 작업을 반드시 구현해야 합니다.

예제: 센서 매니저 등록/해제

@Composable
fun SensorMonitor() {
    val context = LocalContext.current
    var sensorValue by remember { mutableStateOf(0f) }

    DisposableEffect(Unit) {
        val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
        val sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR)
        val listener = object : SensorEventListener {
            override fun onSensorChanged(event: SensorEvent?) {
                event?.values?.firstOrNull()?.let { sensorValue = it }
            }
            override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {}
        }

        sensorManager.registerListener(listener, sensor, SensorManager.SENSOR_DELAY_NORMAL)

        onDispose {
            sensorManager.unregisterListener(listener)
            Log.d("Sensor", "센서 리스너 해제 완료")
        }
    }

    Text("Current Rotation: $sensorValue")
}
  • onDispose에서 센서 리스너 해제 로직 구현[^1]
  • 디지털 일기장에서 기기 움직임 추적 기능 구현 시 활용 가능

2. SideEffect

재구성이 성공적으로 완료된 후 UI 상태와 외부 상태를 동기화할 때 사용됩니다. 코루틴 스코프를 제공하지 않으므로 suspend 함수 호출이 불가능합니다.

예제: 화면 진입 분석 로깅

@Composable
fun DiaryEditorScreen() {
    val analytics = remember { AnalyticsUtil() }
    var diaryContent by remember { mutableStateOf("") }

    SideEffect {
        analytics.logEvent("DiaryEditor_Displayed")
    }

    TextField(
        value = diaryContent,
        onValueChange = { diaryContent = it }
    )
}
  • 재구성 완료 후 분석 이벤트 전송[^1]
  • 디지털 일기장 사용 패턴 분석에 활용

3. rememberCoroutineScope

컴포저블 수명 주기에 바인딩된 코루틴 스코프를 제공하며, 사용자 상호작용 응답 시 코루틴 실행에 적합합니다.

예제: 일기 저장 버튼

@Composable
fun SaveButton() {
    val scope = rememberCoroutineScope()
    var isLoading by remember { mutableStateOf(false) }

    Button(
        onClick = {
            scope.launch {
                isLoading = true
                saveDiaryToCloud()
                isLoading = false
            }
        }
    ) {
        if (isLoading) CircularProgressIndicator() else Text("저장하기")
    }
}

suspend fun saveDiaryToCloud() {
    delay(1000L) // 실제 네트워크 호출 대체
}
  • 버튼 클릭 시 코루틴 실행[^1]
  • 디지털 일기장의 클라우드 동기화 기능 구현 시 필수

API 선택 가이드

상황 추천 API
단일 사용자 이벤트 처리 rememberCoroutineScope
생명주기 기반 리소스 관리 DisposableEffect
UI-외부 상태 동기화 SideEffect
비동기 데이터 로딩 LaunchedEffect

이 예제들은 디지털 일기장 앱 개발 시 자주 발생하는 시나리오를 기반으로 설계되었습니다. 각 API의 특징을 이해하고 상황에 맞게 적용하면 더 안정적인 앱을 구축할 수 있습니다[^1].


결론

Jetpack Compose에서는 사이드 이펙트를 명확하게 분리하여 관리해야 하며, LaunchedEffect 등 Compose가 제공하는 다양한 API를 활용하면 안전하고 예측 가능한 UI 로직을 구현할 수 있습니다. 위의 예제 코드들을 참고하여 상황에 맞게 적절한 사이드 이펙트 API를 사용해 보세요^2^5.

반응형