🤖 Compose

[Compose] SideEffect

콩드로이드 2025. 1. 16. 17:25

SideEffect

- Composable 함수는 입력 값이 변경될 때마다 재실행되며, UI를 업데이트. 하지만, UI 업데이트와 직접적인 관련이 없고, Composable 함수의 재실행 시마다 반복적으로 실행될 필요가 없는 작업이 side-effect

- 즉, 상태 값과 직접적으로 관련 없는 UI 작업들이 Composable 함수에서 불필요하게 재호출되는 것을 방지하기 위해 사용

 


 

LaunchedEffect : Composable 안에서 suspend func 실행

- 하나 이상의 key를 paramater로 받아야 한다 (ex. error.value) 

- key가 바뀌지 않는 이상 같은 코루틴을 사용하고, key가 바뀌면 기존 코루틴을 취소하고 다시 코루틴을 만들어 실행

fun test(
    flavors: State<List<Flavor>>,
    error: State<Throwable?>,
    scaffoldState: ScaffoldState = rememberScaffoldState()
) {
    LaunchedEffect(error.value) {
        val errorMessage = error.value?.message ?: return@LaunchedEffect
        scaffoldState.snackbarHostState.showSnackbar(
            message = errorMessage,
            actionLabel = "show toast"
        )
    }

    // ... flavors를 사용하여 UI를 구성 ...
}

 

 

rememberCoroutineScope

- Composable 내에서 코루틴을 시작하기 위한  방법을 제공하여 더 이상 필요하지 않을 때 적절하게 관리, 취소됨

@Composable
fun MyComposable() {
    val scope = rememberCoroutineScope()
    val snackbarHostState = remember { SnackbarHostState() }

    Button(onClick = {
        scope.launch {
            snackbarHostState.showSnackbar("Hello from coroutine!")
        }
    }) {
        Text("Show Snackbar")
    }

    SnackbarHost(hostState = snackbarHostState)
}

 

 

rememberUpdateState

- 값이 변경되더라도 LaunchedEffect가 다시 시작되지 않도록 하면서 최신 값에 접근해야 할 때 사용 즉, LaunchedEffect 내부에서 사용되는 값이 변경되더라도 LaunchedEffect가 다시 시작되지 않아야 하는 경우 (ex. 타이머, 지속되어야하는 애니메이션 같은 경우)에 LaunchedEffect를 다시 시작하지 않고도 최신 값에 접근 

- rememberUpdatedState는 값이 변경될 때마다 내부적으로 최신 값을 업데이트하지만, LaunchedEffect의 key로 사용되지 않기 때문에 LaunchedEffect에는 영향 X 

@Composable
fun MyScreen(text: String) {
    val currentText = rememberUpdatedState(text) // currentText는 MutableState<String> 타입

    LaunchedEffect(Unit) {
        while (true) {
            delay(1000)
            val currentTextValue = currentText.value // currentText.value를 통해 최신 값에 접근
            println("The latest text is: $currentTextValue") 
        }
    }

    Text(text = text)
}

 

값을 새 값으로 변경해주는 것 밖에 없음..!

 

DisposableEffect:  Composable 함수가 Composition에서 제거될 때 리소스를 해제하기 위해 사용

- lifecycleOwner를 key로 받음

- 리소스 관리에 용이

@Composable
fun MyScreen(lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current) {
    DisposableEffect(lifecycleOwner) {
        val observer = LifecycleEventObserver { _, event ->
            if (event == Lifecycle.Event.ON_RESUME) {
                // 리소스 초기화 또는 시작
            } else if (event == Lifecycle.Event.ON_PAUSE) {
                // 리소스 해제 또는 중지
            }
        }
        lifecycleOwner.lifecycle.addObserver(observer)

        onDispose {
            lifecycleOwner.lifecycle.removeObserver(observer)
        }
    }
}

 

 

SideEffect

 

- 매 recomposition마다 호출됨 (이게 헷갈릴 수 있다)

- Composable 함수가 처음 Composition에 추가될 때, SideEffect 내부의 코드가 실행 O
- Composable 함수가 재구성될 때, SideEffect 내부의 코드는 다시 실행 X
- Composable 함수가 Composition에서 제거될 때, SideEffect 내부의 코드는 실행 X

@Composable
fun MyScreen(count: Int) {
    SideEffect {
        println("SideEffect called!") 
    }

    Text("Count: $count")
}

 SideEffect called!는 한번만 찍힌다..!

즉, 매 Recomposition마다 호출은 되지만 SideEffect { } 내부가 실행되진 않는다! 

그래서 로그를 남기거나, analytics 이벤트를 전송하는 작업들이 SideEffect에 넣기 좋다 

 

 

productState: 비 compose 상태를 compose 상태로 변환

- 비동기적로 상태를 업데이트하고, 그 결과를 State<T>로 반환하는 함수

@Composable
fun MyScreen() {
    val counterState = produceState(initialValue = 0) {
        while (true) {
            delay(1000)
            value++ 
        }
    }

    Text("Counter: ${counterState.value}")
}

ProduceStateScope를 통해 상태를 업데이트..! 

- produceState는 내부적으로 remember를 사용하여 mutableStateOf(initialValue)를 생성
- LaunchedEffect(Unit)을 사용하여 비동기 작업을 시작
- producer lambda 내부에서 ProduceStateScope를 통해 상태를 업데이트하면, mutableStateOf의 값이 변경 (예제에선 value ++)

 

 

derivedStateOf: Compose에서 다른 상태 값을 기반으로 새로운 상태 값을 계산하여 생성하는 함수

- 상태의 값이 바뀌지 않으면 그 값을 유지 (derivedStateOf는 내부적으로 계산된 값을 캐싱하고 있기 때문에, 값이 같다면 value 변경 X)

- 계산된 값이 바뀔 때만 state의 value가 바뀜  -> 불필요한 재구성 방지: derivedStateOf는 필요한 경우에만 상태 값을 다시 계산하여 업데이트

@Composable
fun MyScreen() {
    var counter by remember { mutableStateOf(0) }
    val doubledCounter by remember { derivedStateOf { counter * 2 } }

    Column {
        Button(onClick = { counter++ }) { Text("Increment Counter") }
        Text("Counter: $counter")
        Text("Doubled Counter: $doubledCounter")
    }
}

 

 

snapshotFlow

- Compose State를 Flow로 변환하여 State 값의 변경 사항을 비동기적으로 collect하도록 하는 함수

- 이전에 방출한 값과 다를 경우에만 방출 즉, State 값이 변경되면 snapshotFlow는 새로운 값을 Flow로 방출

var counter by remember { mutableStateOf(0) }

LaunchedEffect(Unit) {
    snapshotFlow { counter }
        .collect { newCounter ->
            println("Counter updated: $newCounter")
        }
}

Counter updated: 0 만 찍히고 그 후엔 안 찍힘 

 

 

'🤖 Compose' 카테고리의 다른 글

[Compose] Compose 주의점  (0) 2025.01.17
[Compose] Animation  (0) 2025.01.08
[Compose] State, StateHoisting  (0) 2025.01.07
[Compose] Snackbar  (0) 2025.01.07
[Compose] ConstraintLayout, ConstraintSet  (0) 2025.01.07