
JetPack Compose는 초기 컴포지션 시 처음으로 컴포저블을 실행할 때 컴포지션에서 UI를 기술하기 위해 호출하는 컴포저블을 추적한다. 그런 다음 앱 상태가 변경되면 리컴포지션을 한다. 리컴포지션은 Jetpack Compose가 상태 변경사항에 따라 컴포저블을 다시 실행한 다음 변경사항을 반영하도록 컴포지션을 업데이트 하는 것이다.

컴포지션은 초기 컴포지션을 통해서만 생성이 되고 리컴포지션을 통해서만 업데이트 될 수 있다. 컴포지션을 수정하는 유일한 방법은 리컴포지션을 통하는 것이다. 컴포저블은 컴포지션을 시작하고 0회 이상 재구성되고 컴포지션을 종료한다. 리컴포지션은 일반적으로 State<T> 객체가 변경되면 트리거 된다. Compose는 이러한 객체를 추적하고 컴포지션에서 특정 State<T>를 읽는 모든 컴포저블 및 컴포저블 중 건너뛸 수 없는 모튼 컴포저블을 실행한다.
@Composable
fun MyComposable() {
Column {
Text("Hello")
Text("World")
}
}

컴포저블이 여러 번 호출되면 컴포지션에 여러 인스턴스가 배치된다. 색상이 다른 요소는 요소가 별도의 인스턴스임을 나타낸다.
1. 컴포지션 내 컴포저블 분석
컴포지션 내 컴포저블의 인스턴스는 호출 사이트(call site)로 식별된다. 호출 사이트는 컴포저블이 호출되는 소스 코드의 위치이다. 호출 사이트는 컴포지션 내 위치와 UI 트리거에 영향을 미친다. 여러 호출사이트로부터 컴포저블을 호출하면 컴포저블의 여러 인스턴스가 생성된다.
리컴포지션을 하는 동안, 컴포저블이 이전 컴포지션 동안과는 다른 다른 컴포저블을 호출했다고 해보자. 리컴포지션을 하는 동안 Compose는 어떤 컴포저블이 호출되었는 지 여부를 식별하고 두 컴포지션에서 호출된 컴포저블에 대해 입력이 변경되지 않은 경우 Compose는 재구성을 방지한다
@Composable
fun LoginScreen(showError: Boolean) {
if (showError) {
LoginError()
}
LoginInput() // This call site affects where LoginInput is placed in Composition
}
@Composable
fun LoginInput() { /* ... */ }
위 코드 스니펫에서 LoginScreen 조건부로 LoginError 컴포저블을 호출하고, LoginInput 컴포저블을 항상 호출한다. 각각 호출에는 컴파일러가 고유하게 식별하는 데 사용할 고유한 호출 사이트와 소스 위치가 있다.

LoginInput 첫 번째 호출에서 두 번째 호출로 바뀌었지만 LoginInput 인스턴스는 리컴포지션 간에 변경된 매개변수가 없기 때문에 Compose가 LoginInput 호출을 건너뛴다.
2. Column 컴포지션 - 리스트 하단에 추가하는 경우
컴포저블을 여러번 호출하면 컴포지션에도 여러번 추가된다. 아래 예제와 같이 동일한 호출 사이트(동일 소스 코드 위치)에서 여러 번 호출하는 경우 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)
}
}
}
위의 예에서 목록 맨 아래에 새 항목 movie가 추가되면 목록에서 위치가 변경되지 않았으므로 Compose는 컴포지션에 이미 있는 인스턴스를 재사용할 수 있으므로 상위에 있는 해당 인스턴스들을 리컴포지션 하지 않는다.

3. Column 컴포지션 - 리스트 상단에(혹은 중간) 추가하는 경우
그러나 목록의 맨 위나 중간에 항목을 추가하거나 제거하거나 재정렬하여 목록이 변경되면 위치를 변경한 모든 호출에서 재구성이 발생한다.
@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)
/* ... */
}
}

4. Column 컴포지션 - 각 항목 인스턴스에 Key를 매칭한 경우
key 컴포저블을 사용하면, 컴포즈는 런타임에 트리의 특정 부분을 식별하는데 사용할 값을 알릴 수 있는 방법을 제공한다.
하나 이상의 값이 전달된 key 컴포저블로 코드 블록을 래핑하면 해당 값이 결합되어 Compose에서 해당 인스턴스를 식별하는데 사용된다. 이 값은 전역적으로 고유할 필요는 없으며 호출 사이트에서 컴포저블 호출 간에만 고유해야 한다.
@Composable
fun MoviesScreen(movies: List<Movie>) {
Column {
for (movie in movies) {
key(movie.id) { // Unique ID for this movie
MovieOverview(movie)
}
}
}
}
위와 같이 목록의 요소가 변경되더라도 Compose는 개별 MovieOverview 호출을 인식하고 재사용할 수 있다.

일부 컴포저블에는 컴포저블에 대한 기본 제공 key가 있다. 예를 들면 LazyColumn 은 item 관련 특정 커스텀 key 값을 수락한다.
@Composable
fun MoviesScreen(movies: List<Movie>) {
LazyColumn {
items(movies, key = { movie -> movie.id }) { movie ->
MovieOverview(movie)
}
}
}
5. 요약
1. 컴포저블은 초기 컴포지션 이후 리컴포지션을 통해서만 업데이트 될 수 있다.
2. 컴포지션에서 컴포저블의 인스턴스는 호출 사이트로 식별된다. 이는 컴포저블이 호출되는 소스 코드의 위치이다
3. 별다른 식별자 없이 같은 호출 사이트에서 여러 인스턴스가 생성될 경우, 실행 순서가 컴포저블을 구별하는 데에 사용된다.
- column의 최하단에 아이템이 추가될 경우 기존 아이템의 순서는 변경되지 않으므로 리컴포지션이 발생하지 않는다.
- column의 상단이나 중간에 아이템이 추가될 경우 순서가 변경된 아이템 모두 리컴포지션이 발생한다
4. Column 아이템에 Key를 매칭하면 순서가 변경되어도 식별이 가능하기 때문에 해당 컴포저블은 리컴포지션이 발생하지 않는다.
출처
https://developer.android.com/jetpack/compose/lifecycle#skipping
'안드로이드 > UI' 카테고리의 다른 글
Compose에서 "donut-hole skipping" 란 무엇인가 (0) | 2022.08.17 |
---|---|
Compose 상태관리 (0) | 2022.07.14 |
Compose란 무엇인가 (0) | 2022.07.14 |