Jetpack Compose Lifecycle

img

Composable 함수는 composition을 시작하고 0회 이상 recomposition되고 종료된다.

Composition 내 composable 함수의 인스턴스는 Call Site로 식별된다.

Call Site : 함수가 호출되는 소스코드의 위치

Compose 컴파일러는 각 call site를 고유한 것으로 간주한다. 따라서 같은 Composable 함수를 2번 호출하면 별도의 인스턴스로 UI에 배치된다.

@Composable
fun MyComposable() {
    Column {
        Text("Hello")
        Text("World")
    }
}

img

Smart Recomposition

Recomposition시 이전 컴포지션 시 호출한 것과 다른 composable을 호출할 경우 Compose는 호출된 Composable과 호출되지 않은 Composable을 구분한다. 호출된 Composable의 경우 input이 변경되지 않은 경우 recomposition이 일어나지 않는다.

@Composable
fun LoginScreen(showError: Boolean) {
    if (showError) {
        LoginError()
    }
    LoginInput() // This call site affects where LoginInput is placed in Composition
}

@Composable
fun LoginInput() { /* ... */ }

img (색이 같으면 같은 인스턴스를 나타낸다)

LoginInput Composable은 LoginScreen이 recomposition되었을 때 변경된 매개변수가 없으므로 recomposition되지 않는다.

Help Smart Recomposition

Composable을 여러 번 호출하면 composition에 여러 번 추가된다. 같은 call site에서 여러 번 호출하는 경우 이를 식별하기 위해 기본적으로 실행 순서를 통해 식별한다. 대부분은 문제 없이 작동하지만 때때로 원치 않는 동작이 일어날 수 있다.

@Composable
fun MoviesScreen(movies: List<Movie>) {
    Column {
        for (movie in movies) {
            // MovieOverview composables are placed in Composition given its
            // index position in the for loop
            MovieOverview(movie)
        }
    }
}

img movies 리스트의 끝에 새 Movie가 추가될 경우에는 기존 MovieOverview의 순서가 변경되지 않으므로 recomposition이 일어나지 않는다.

그러나 list의 중간에 Movie가 추가되거나 삭제되거나 sorting이 일어날 경우 위치가 변경된 MovieOverview는 모두 recomposition이 일어난다. img (색이 다르면 다른 인스턴스임을 나타낸다)

이를 해결하려면 Composable의 실행 순서를 통해 식별하는 것이 아닌 다른 방법을 통해 식별할 수 있도록 해야 한다(ex. Movie의 고유한 ID 값). 이는 Composable 함수 내에 key를 통해 사용할 수 있다.

@Composable
fun MoviesScreen(movies: List<Movie>) {
    Column {
        for (movie in movies) {
            key(movie.id) { // Unique ID for this movie
                MovieOverview(movie)
            }
        }
    }
}

key는 call site 내의 Composable간에만 unique하면 된다.

몇몇 Composable은 key 기능을 내장해 놓은 경우가 있다(LazyColumn …).

Skipping Recomposition

Composition에 composable이 있는 경우 모든 input 값이 Stable하고 변경되지 않았으면 recomposition을 건너뛴다.

Stable Type

  • 두 인스턴스의 equals가 동일한 두 인스턴스는 같은 인스턴스이다.
  • Public property가 변경되면 Composition에게 알린다.
  • 모든 public property가 stable하다.

위와 관계 없이 stable함을 만족하는 타입은

  • 모든 primitive types: Boolean, Int, Long
  • String
  • 함수 타입(람다)

변경할 수 없는(Immutable) 타입의 경우 변경되지 않기 때문에 Composition에 변경되었다는 사실을 알릴 필요가 없다. 따라서 stable함을 쉽게 만족할 수 있다.

Stable하지만 변경 가능한(mutable)한 타입은 대표적으로 MutableState가 있다. MutableState의 경우 value 속성이 변경되면 composition에게 알림을 전달하기 때문에 MutableState는 Stable하다고 간주할 수 있다.

Compose가 Stable함을 추론할 순 없지만 Stable하다고 간주하려면 @Stable annotation을 사용한다.

// Marking the type as stable to favor skipping and smart recompositions.
@Stable
interface UiState<T : Result<T>> {
    val value: T?
    val exception: Throwable?

    val hasError: Boolean
        get() = exception != null
}

Compose는 UiState가 Stable하다고 간주하여 Smart recomposition을 선호하게 된다.


© 2021. All rights reserved.

Powered by Hydejack v9.1.6