시배's Android

Compose Docs | Lifecycle of composables 본문

Android/Compose Docs

Compose Docs | Lifecycle of composables

si8ae 2023. 8. 1. 18:29
 

컴포저블 수명 주기  |  Jetpack Compose  |  Android Developers

컴포저블 수명 주기 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 이 페이지에서는 컴포저블의 수명 주기에 관해 알아보며 Compose에서 컴포저블에 재구성

developer.android.com

Lifecycle overview

Composition은 앱의 UI를 설명하는 데 사용되며, Composable 함수들을 실행하여 생성됩니다. Composition은 UI를 설명하는 Composable 함수들의 트리 구조입니다.

Jetpack Compose는 처음으로 Composable 함수들을 실행할 때 초기 구성(initial composition) 중에 사용된 Composable 함수들을 추적합니다. 그런 다음 앱의 상태가 변경되면 Jetpack Compose는 재구성(recomposition)을 예약합니다. 재구성이란 Jetpack Compose가 상태 변경에 응답하여 변경될 수 있는 Composable 함수들을 다시 실행하고, 그 결과로 Composition을 업데이트하는 것을 말합니다.

Composition은 초기 구성에 의해 생성되고, 재구성을 통해서만 업데이트될 수 있습니다. Composition을 수정하는 유일한 방법은 재구성을 통하는 것입니다.

재구성은 일반적으로 State<T> 객체의 변경으로 인해 트리거됩니다. Compose 이러한 상태(State) 객체들을 추적하고 해당 특정 State<T> 읽는 Composition 내의 모든 Composable 함수들과, 스킵할 없는 호출되는 다른 Composable 함수들을 실행합니다.

 

만약 Composable 함수가 여러 호출된다면, 여러 개의 인스턴스가 Composition 내에 배치됩니다. 호출은 Composition 내에서 고유한 라이프사이클을 갖습니다.

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

Anatomy of a composable in Composition

Composition 내에서 Composable 함수의 인스턴스는 해당 함수를 호출한 위치에 따라 식별됩니다. Compose 컴파일러는 각 호출 위치를 서로 다른 것으로 간주합니다. 여러 호출 위치에서 Composable 함수를 호출하면 Composition 내에 해당 함수의 여러 인스턴스가 생성됩니다.

재구성 중에 Composable 함수가 이전 구성과 다른 다른 Composable 함수들을 호출한다면, Compose는 어떤 Composable 함수들이 호출되었는지 혹은 호출되지 않았는지를 식별하고, 두 구성에서 모두 호출된 Composable 함수들 중 입력이 변경되지 않은 경우, 재구성하지 않고 건너뛰게 됩니다.

식별성(identity)을 보존하는 것은 Side-Effect을 해당 Composable 함수와 연관시키는 데 매우 중요합니다. 이렇게 함으로써 Side-Effect는 매번 재구성될 때마다 재시작하는 대신 정상적으로 완료될 수 있습니다.

다음 예를 고려해봅시다:

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

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

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

위의 코드 스니펫에서, LoginScreen 조건에 따라 LoginError Composable 호출하고 항상 LoginInput Composable 호출합니다. 호출은 고유한 호출 위치와 소스 위치를 갖고 있으며, 이는 컴파일러가 호출을 고유하게 식별하는 사용됩니다.

LoginInput 처음에 호출되는 것에서 번째로 호출되는 것으로 변경되더라도, LoginInput 인스턴스는 재구성을 거쳐도 보존될 것입니다. 또한 LoginInput 재구성을 거쳐서 변경된 매개변수가 없기 때문에, Compose LoginInput 호출을 건너뛰게 됩니다.

Add extra information to help smart recompositions

Composable 함수를 여러 호출하면 해당 함수가 Composition 여러 추가됩니다. 같은 호출 위치에서 Composable 함수를 여러 호출할 , Compose 호출을 고유하게 식별하는 데에 필요한 정보가 없기 때문에 실행 순서도 호출 위치와 함께 사용하여 인스턴스를 구분합니다. 동작은 때때로 필요한 동작이지만, 경우에 따라 원하지 않는 동작을 초래할 수도 있습니다.

@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)
        }
    }
}

위의 예시에서 Compose 실행 순서를 호출 위치와 함께 사용하여 Composition 내에서 인스턴스를 고유하게 유지합니다. 목록의 아래에 영화가 추가되면, Compose 이미 Composition 있는 인스턴스들을 재사용할 있습니다. 왜냐하면 해당 인스턴스들은 목록에서의 위치가 변경되지 않았으며, 따라서 이들 인스턴스들의 영화 입력은 동일하기 때문입니다.

하지만, 만약 영화 목록이 리스트의 위나 중간에 항목을 추가하거나 삭제하거나 재정렬한다면, 목록에서 입력 매개변수의 위치가 변경된 MovieOverview 호출들은 모두 재구성을 일으킬 것입니다. 이는 매우 중요한 사항인데, 예를 들어 MovieOverview 부작용(side effect) 사용하여 영화 이미지를 가져오는 경우를 생각해보면 있습니다. 만약 부작용이 진행 중일 재구성이 발생하면 해당 Side-Effect 취소되고 다시 시작될 있습니다.

@Composable
fun MovieOverview(movie: Movie) {
    Column {
        // Side effect explained later in the docs. If MovieOverview
        // recomposes, while fetching the image is in progress,
        // it is cancelled and restarted.
        val image = loadNetworkImage(movie.url)
        MovieHeader(image)

        /* ... */
    }
}

이상적으로, 우리는 MovieOverview 인스턴스의 식별자를 해당 인스턴스에 전달된 영화의 식별자와 연결된 것으로 생각하고자 합니다. 만약 우리가 영화 목록을 재정렬한다면, 이상적으로는 Composition 트리 내의 인스턴스들을 영화의 순서에 따라 재배치하고, MovieOverview Composable 함수를 다른 영화 인스턴스로 재구성하는 대신에 영화의 순서대로 재배치할 있도록 합니다. Compose 트리 내의 특정 부분을 식별하는 데에 어떤 값을 사용할지 런타임에 알려주는 방법을 제공합니다: key Composable 함수를 사용하는 것입니다.

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

\

Skipping if the inputs haven't changed

만약 Composable 함수가 Composition 내에 이미 존재한다면, 모든 입력이 stable이고 변경되지 않았다면 재구성을 건너뛸 수 있습니다.

stable 타입은 다음과 같은 규약을 따라야 합니다:

  • 두 인스턴스에 대한 equals 메서드의 결과는 항상 같아야 합니다.
  • 타입의 공개(public) 속성이 변경되면 Composition에 알려져야 합니다.
  • 모든 공개 속성 타입 역시 안정적이어야 합니다.

이 규약을 따르는 일부 중요한 일반 타입들은 Compose 컴파일러가 안정적으로 처리하며, @Stable 주석을 사용하여 명시적으로 안정적으로 표시하지 않더라도 안정적으로 간주합니다.

이러한 안정적인 타입들로는 다음이 포함됩니다:

  • 모든 기본 값 타입: Boolean, Int, Long, Float, Char 등
  • 문자열 (Strings)
  • 모든 함수 타입 (람다)

이러한 타입들은 불변(immutable)하기 때문에 위 규약을 따를 수 있습니다. 불변 타입은 절대 변경되지 않으므로 Composition에 변경 사항을 알릴 필요가 없으며, 따라서 이 규약을 따르기가 훨씬 쉽습니다. 모든 깊은 불변 타입들은 안전하게 안정적인 타입으로 간주될 수 있습니다.

 

하지만, 하나의 주목할만한 안정적인(mutable) 타입으로는 Compose의 MutableState 타입이 있습니다. MutableState에 값이 저장되면, 해당 상태 객체는 Compose에게 State의 .value 속성에 대한 변경 사항을 알려줄 수 있는 안정적인 타입으로 간주됩니다.

Composable 함수의 모든 매개변수 타입이 안정적인 경우, 매개변수 값들은 UI 트리에서 Composable 함수의 위치를 기준으로 동등성(equality)을 비교합니다. 이전 호출과 동일한 경우 재구성이 건너뛰어집니다.

Compose는 Composable 함수의 모든 입력이 안정적이고 변경되지 않은 경우 재구성을 건너뛰게 됩니다. 비교는 equals 메서드를 사용하여 이루어집니다.

 

Compose는 타입이 안정적인지 여부를 증명할 수 있는 경우에만 안정적인 타입으로 간주합니다. 예를 들어, 인터페이스는 일반적으로 안정적이 아니라고 간주되며, 구현이 불변할 수 있는 타입의 공개 속성을 가지는 타입들 또한 안정적이지 않습니다.

만약 Compose 타입이 안정적이라고 추론할 없다면, 그러나 강제로 Compose에게 해당 타입을 안정적으로 처리하도록 하려면 @Stable 주석을 사용하여 표시합니다.

// 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
}

위의 코드 스니펫에서 UiState 인터페이스이기 때문에 Compose 일반적으로 타입을 안정적이 아닌 것으로 간주할 있습니다. @Stable 주석을 추가함으로써 타입이 안정적이라고 알려줍니다. 이는 Compose 스마트한 재구성을 선호하도록 하며, 또한 해당 인터페이스를 매개변수 타입으로 사용하는 경우 모든 구현을 안정적으로 처리하도록 합니다.

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

Compose Docs | Jetpack Compose Phases  (0) 2023.08.10
Compose docs | Side-effects in Compose  (0) 2023.08.08
Compose Docs | Modifiers  (0) 2023.08.07
Compose Docs | State and Jetpack Compose  (0) 2023.07.31
Compose Docs | Thinking in Compose  (0) 2023.07.31