Side Effect의 정의
- 순수 함수: 입력만으로 결과를 계산하며, 외부 상태에 영향을 미치지 않음.
- 부작용(Side Effect): 함수가 외부 상태를 변경하거나 외부 요인에 의존할 때 발생
예시 : 전역 변수 값 변경, 파일 읽기/쓰기, 네트워크 요청, 데이터베이스 상태 변경, UI 업데이트, 예외 발생

Composable 함수가 리컴포지션 될 때 사용할 suspend fun(정지 함수/비동기 작업) 실행환경을 제공하는 스코프이다.
해당 함수는 리컴포지션 될때마다 실행되는 것이 아닌 key가 변경될 때마다 실행된다.
매개변수로 1~N개의 key를 넣을 수 있으며, 만약 매개변수를 넣지 않거나 Unit을 입력 시 최초 한 번(첫 컴포지션) 때만 실행되고, 이후엔 재실행되지 않는다.
@Composable
fun MyScreen() {
var text by remember { mutableStateOf("초기 상태") }
LaunchedEffect(Unit) {
delay(2000)
text = "변경된 상태" // 상태 업데이트
}
Text(text = text) // 상태 기반으로 UI 업데이트
}

컴포저블이 활성화될 때 실행될 행동과, 컴포저블이 비활성화될 때 실행될 행동을 정의할 수 있는 스코프이다.
DisposableEffect의 실행은 첫 컴포지션이 이루어 질때와 Key가 변경될 때 실행된다. 즉, 리컴포지션이 발생한다고 무조건 실행되는 것은 아니다.
DisposableEffect의 onDispose는 key가 변경되지 않아도, 컴포저블이 비활성화되면 무조건 실행된다.
@Composable
fun MyScreen(show: Boolean) {
if (show) {
DisposableEffect(Unit) {
println("DisposableEffect 시작")
onDispose {
println("컴포저블 비활성화됨 - onDispose 호출")
}
}
}
}
@Composable
fun MyScreen() {
val coroutineScope = rememberCoroutineScope()
DisposableEffect(Unit) {
val job = coroutineScope.launch {
someSuspendFunction() // 정지 함수 실행
}
onDispose {
job.cancel() // 코루틴 취소 및 리소스 정리
}
}
}

Compoable 함수 외부의 상태를 작업 하기 위한 스코프이다.
동기적인 함수이다.
Compoable이 리컴포지션 될 때마다 실행된다.
Compoable 내부의 상태는 Compoable 자체가 수행할 수 있으므로, SideEffect에서는 Compoable에서 관리하지 않는 외부 상태만 작업한다.
주요 사용처 : 로그 기록, Compose 외부 시스템 상태 업데이트, 외부 객체와의 상태 동기화
//로그 기록
@Composable
fun LogExample(name: String) {
SideEffect {
println("현재 이름: $name")
}
Text(text = "Hello, $name!")
}
//Compose 외부 시스템 상태 업데이트
@Composable
fun UpdateViewExample(counter: Int, externalView: TextView) {
SideEffect {
externalView.text = "Counter: $counter"
}
}
//외부 객체와의 상태 동기화
@Composable
fun SynchronizeStateWithExternalSystem(state: String, externalSystem: ExternalSystem) {
SideEffect {
externalSystem.updateState(state)
}
}
컴포저블의 생명주기와 연동: 컴포저블이 제거되면 스코프 내의 모든 코루틴이 취소된다. (LaunchedEffect와 같다.)
비동기 작업 실행: 비동기 작업(정지 함수 호출 등)을 관리하기 위해 사용된다.(LaunchedEffect와 같다.)
재구성 시 스코프 재생성 없음: 리컴포지션이 발생해도 이전에 생성된 스코프를 재사용한다. (LaunchedEffect와 다르다.)
@Composable
fun UpdateExternalView(externalView: TextView) {
val coroutineScope = rememberCoroutineScope()
Button(onClick = {
coroutineScope.launch {
delay(500)
externalView.text = "Updated after delay!"
Toast.makeText(context, "executed in SideEffectScreen Lifecycle", Toast.LENGTH_SHORT).show()
}
}) {
Text("Update TextView")
}
}
상태변경을 추적하는 함수
remember{ mutableState(초기값) }은 state를 만들고 캐싱하여 상태를 유지한다. 값을 변경하려면 value를 사용하여 변경해야지 적용된다.
하지만 만약 초기값으로 함수의 매개변수를 넣게 된다면, remember은 이를 추적하지 않으므로, 매개변수 값이 변해도 value가 변경되지 않는다.
@Composable
private fun ResultText(result: Int) {
//초기의 result로 state가 만들어지고, result가 아무리 변해도 변수값은 변경되지 않음
val rememberResult by remember { mutableIntStateOf(result) }
rememberUpdateState는 remember에서 초기화과정을 거친다.@Composable
fun <T> rememberUpdatedState(newValue: T): State<T> = remember {
mutableStateOf(newValue)
}.apply { value = newValue }
produceState는 비동기적으로 데이터를 로드하고, 해당 데이터를 상태로 관리할 수 있도록 해주는 State 객체를 반환
비동기 작업이 완료되면 상태를 자동으로 업데이트하여 UI가 최신 상태를 반영할 수 있게 돕는다.
컴포지션 및 리컴포지션 때마다 실행 : 상태값이 바뀌지는 않아도, 비동기 작업으로 인한 자원소모가 될 수 있음
produceState는 두 개의 매개변수를 받는다:
initialValue: 초기값을 제공 (상태의 초기 값)producer: 비동기 작업을 수행하는 코드 블록{}으로, 여기서 비동기 작업을 시작하고, 해당 작업이 완료되면 결과로 상태를 업데이트한다.@Composable
fun FetchNetworkData() {
val networkData by produceState(initialValue = "Loading...") {
// 네트워크 요청 시뮬레이션
val data = fetchDataFromNetwork() // 비동기 네트워크 요청 함수
value = data // 결과 값 업데이트
}
Text(networkData) // 상태를 텍스트로 표시
}
// 비동기 네트워크 요청 함수
suspend fun fetchDataFromNetwork(): String {
delay(3000) // 네트워크 요청 시뮬레이션
return "Fetched Network Data!"
}
Compose 상태를 Flow로 변환 : Compose의 상태를 읽고, 상태가 변경될 때마다 Flow가 새로운 값을 발행
읽기 작업만 지원 : snapshotFlow 내부에서 Compose 상태를 읽기만 가능하며, 상태를 수정하는 작업은 허용되지 않는다.
Compose 상태 변경을 비동기적으로 처리 : UI 상태가 변할 때 이를 Flow를 통해 비동기 작업(예: 네트워크 요청, 데이터 로깅 등)과 연결할 수 있다.
snapshotFlow 내부에서 외부 상태를 읽는 경우 일관성 문제가 발생할 수 있음.
snapshotFlow는 Compose 상태 변경을 감지하는 데 특화되어 있으므로, 다른 일반적인 Flow 작업에는 적합하지 않다.
@Composable
fun SnapshotFlowExample() {
val count = remember { mutableStateOf(0) }
// count 상태를 Flow로 변환
val countFlow = snapshotFlow { count.value }
// Flow 수집
LaunchedEffect(Unit) {
countFlow.collect { value ->
//값이 변경될 때마다 Flow를 관찰하여 로그를 출력
Log.d("SnapshotFlow", "Count value: $value")
}
}
// UI
Column {
Text("Count: ${count.value}")
Button(onClick = { count.value++ }) {
Text("Increment")
}
}
}