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.
⁂