Compose 기초 - Composable의 수명주기, Smart Recomposition

Composable의 수명주기 (원문)

💡개념이 헷갈리거나 제가 잘 모르거나 많은 사람들이 잘 모를 것 같은 것 위주로 정리

Composable의 수명 주기

Initial Composition 이후 0회 이상 Recomposition 되고 Composition이 종료된다. lifecycle

Smart Recomposition에 도움이 되는 정보 추가하기

목록 중간에 항목을 추가/삭제하는 경우, 목록이 재정렬되는 경우 모든 항목은 Recomposition의 대상이 된다. new-top

Column 또는 LazyColumn Composable 아래에서 key Composable을 사용하면 변동이 없는 항목을 Recomposition 대상에서 제외시킬 수 있다.

@Composable
fun Screen() {
    var items by remember {    
        mutableStateOf((0 until 1000).toList())
    }
    val scope = rememberCoroutineScope()

    ItemList(
        items = items,
        onItemClick = {
            // scope 안에서 데이터를 변경하면 key가 제 역할을 하지 못한다. ViewModel에서 데이터를 변경해도 마찬가지일 것. rememberUpdatedState()의 개념이 힌트일지도 모르겠다.
            // LazyColumn에선 key가 제대로 역할을 못하더라도 LazyColumn이 화면에 보여지는 항목들만 Recomposition 대상으로 삼기 때문에 성능문제가 크진 않다.
            // scope.launch {
                val newItems = items.takeLast(items.size - 1)
                items = newItems
            // }
        },
    )
}

@Composable
private fun ItemList(items: List<Int>, onItemClick: () -> Unit) {
    LazyColumn(Modifier.fillMaxSize()) {
        items(
            items = items,
            key = { item -> item },
        ) { item ->
            // 이 위치에선 아래와 같이 `@Composable` 함수를 호출하지 않고 Box { ... } 처럼 직접 풀어쓰면 key가 제대로 역할을 하지 못한다. (원인 모름)
            // 이 위치에선 아래와 같이 `@Composable` 함수를 호출하는 것 외에 실행코드 한 줄이라도 있으면 key가 제대로 역할을 하지 못한다. 예) Log.e("BSSCCO", "$item"). (원인 모름)
            Item(item, onClick = onItemClick)
        }
    }
}

@Composable
private fun Item(item: Int, onClick: () -> Unit) {
    Box(
        modifier = Modifier
            .clickable(onClick = onClick)
            .fillMaxWidth()
            .height(50.dp),
        contentAlignment = Alignment.Center,
    ) {
        Log.e("BSSCCO", "$item")
        Text(item.toString())
    }
}

key 사용 시 주의할 점

coroutineScope 안에서 데이터를 변경하면 key가 제대로 역할을 하지 못한다. ViewModel에서 데이터를 변경해도 마찬가지일 것. rememberUpdatedState()의 지연된 상태접근 개념이 원인의 힌트가 될지도 모르겠다..

items 안에선 Composable 함수를 호출하지 않고 Box { ... } 처럼 직접 풀어쓰면 key가 제대로 역할을 하지 못한다. (원인 모름)

items 안에선 Composable 함수를 호출하는 것 외에 실행코드 한 줄이라도 있으면 key가 제대로 역할을 하지 못한다. 예) Log.e("BSSCCO", "$item"). (원인 모름)

LazyColumn에선 key가 제대로 역할을 못하더라도 LazyColumn은 화면에 보여지는 항목들만 Recomposition 대상으로 삼기 때문에 성능문제가 크진 않다.

Recomposition에서 제외되는 조건

모든 입력(@Composable 함수의 매개변수, 함수 안에서 사용하고 있는 State<T> 변수 등)이 안정적이고 변경되지 않았으면 건너띈다.

안정적인 자료형

  • 모든 원시 값 유형: Boolean, Int, Long, Float, Char 등
  • 문자열
  • 모든 함수 유형(람다)
  • 완전히 변경 불가능한 모든 유형

변경되지 않았는지 비교하는 방법

두 인스턴스의 equals 결과가 동일한지 비교한다.

안정적인 것으로 강제하기

Compose가 유형이 안정적이라고 추론할 수 없지만 안정적인 것으로 간주하도록 하려면 @Stable 어노테이션을 사용한다.

  @Stable
  interface UiState<T : Result<T>> {  
      val value: T?  
      val exception: Throwable?  
    
      val hasError: Boolean    
          get() = exception != null
  }