Android/Compose

[Compose] Compose의 Side-Effect(3)

JunsuKim 2023. 5. 15.
728x90

이전 포스팅에서 이어서 작성.

https://jjunsu.tistory.com/383

 

[Compose] Compose의 Side-Effect(2)

Compose의 Side-Effect(1) 이전 내용에서 이어서 작성. https://jjunsu.tistory.com/382 [Compose] Compose의 Side-Effect(1) Compose의 부수 효과 부수 효과는 구성 가능한 함수의 범위 밖에서 발생하는 앱 상태에 관한 변

jjunsu.tistory.com


derivedStateOf: 하나 이상의 상태 객체를 다른 상태로 변환

특정 상태가 계산되거나 다른 상태 개체에서 파생되는 경우 derivedStateOf를 사용한다.

derivedStateOf를 사용하면 계산에 사용된 상태 중 하나가 변경될 때마다 계산이 실행된다.

@Composable
fun TodoList(highPriorityKeywords: List<String> = listOf("Review", "Unblock", "Compose")) {

    val todoTasks = remember { mutableStateListOf<String>() }

    // Calculate high priority tasks only when the todoTasks or highPriorityKeywords
    // change, not on every recomposition
    val highPriorityTasks by remember(highPriorityKeywords) {
        derivedStateOf { todoTasks.filter { it.containsWord(highPriorityKeywords) } }
    }

    Box(Modifier.fillMaxSize()) {
        LazyColumn {
            items(highPriorityTasks) { /* ... */ }
            items(todoTasks) { /* ... */ }
        }
        /* Rest of the UI where users can add elements to the list */
    }
}
  • 위 코드에서 DerivateStateOf는 todoTasks가 변경될 때마다 highPriorityTasks 계산이 실행되고, 이에 따라 UI가 업데이트되도록 보장한다.
  • highPriorityTasks가 변경되면 remember 블록이 실행되고 새로운 파생 상태 개체가 생성되어 이전 상태 개체 대신 기억된다.
  • highPriorityTasks를 계산하기 위한 필터링은 비용이 많이 들 수 있으므로 재구성할 때마다가 아닌 목록이 변경될 때만 실행되어야 한다.
  • derivedStateOf에 의해 상태가 업데이트되어도 업데이트가 선언된 Composable이 재구성되지 않는다. Composable는 예의 LazyColumn 내에서 반환된 상태를 읽는 위치의 Composable만 재구성한다.

snapshotFlow: Compose의 상태를 Flow로 변환

snapshotFlow를 사용하여 State<T> 객체를 Cold Flow로 변환한다.

  • Cold Flow: 각각의 소비자가 수집을 시작하면 데이터를 발행(각 호출마다 실행되므로 서로 다른 Flow 객체로부터 수집), 스트림 생성 후 구독하지 않으면 어떤 연산도 발생하지 않는다.

snapshotFlow는 수집될 때 블록을 실행하고 읽은 State 개체의 결과를 내보낸다.

snapshotFlow 블록 내부에서 읽은 State 객체 중 하나가 변경되면 새 값이 이전에 내보낸 갑과 같지 않으면 Flow는 새 값을 수집기로 내보낸다.

val listState = rememberLazyListState()

LazyColumn(state = listState) {
    // ...
}

LaunchedEffect(listState) {
    snapshotFlow { listState.firstVisibleItemIndex }
        .map { index -> index > 0 }
        .distinctUntilChanged()
        .filter { it == true }
        .collect {
            MyAnalyticsService.sendScrolledPastFirstItemEvent()
        }
}
  • listState.firstVisibleItemIndex는 Flow 연산자의 이점을 활용할 수 있는 Flow로 변환된다.

Restarting effects: 효과 다시 시작

LaunchedEffect, produceState, DisposableEffect와 같은 Compose의 일부 효과는 실행 중인 효과를 취소하고 새 키로 새 효과를 시작하는데 사용되는 가변 개수의 인수 키를 사용한다.

EffectName(restartIfThisKeyChanges, orThisKey, orThisKey, ...) { block }

이 동작은 미묘하므로 효과를 다시 시작하는 데 사용된 매개변수가 올바르지 않은 경우 문제가 발생할 수 있다.

  • 필요한 것보다 적은 효과를 다시 시작하면 앱에 버그가 발생할 수 있다.
  • 필요한 것보다 더 많은 효과를 다시 시작하면 비효율적일 수 있다.
@Composable
fun HomeScreen(
    lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
    onStart: () -> Unit, // Send the 'started' analytics event
    onStop: () -> Unit // Send the 'stopped' analytics event
) {
    // These values never change in Composition
    val currentOnStart by rememberUpdatedState(onStart)
    val currentOnStop by rememberUpdatedState(onStop)

    DisposableEffect(lifecycleOwner) {
        val observer = LifecycleEventObserver { _, event ->
            /* ... */
        }

        lifecycleOwner.lifecycle.addObserver(observer)
        onDispose {
            lifecycleOwner.lifecycle.removeObserver(observer)
        }
    }
}
  • currentOnStart 및 currentOnStop은 rememberUpdatedState 사용으로 인해 Composition에서 해당 값이 변경되지 않기 때문에 DisposableEffect 키로 필요하지 않다.
  • lifecycleOwner를 매개변수로 전달하지 않고 변경하면 HomeScreen이 재구성되지만 DisposableEffect가 삭제되거나 다시 시작되지 않는다.
728x90

'Android > Compose' 카테고리의 다른 글

[Compose] LazyColumn이란?  (0) 2023.07.11
[Compose] @Preview 분석  (0) 2023.06.27
[Compose] Compose의 Side-Effect(2)  (0) 2023.05.09
[Compose] Compose의 Side-Effect(1)  (0) 2023.04.25
[Compose] Compose에서의 상태  (0) 2023.04.03

댓글