Lifecycle of composables
이 페이지에서는 컴포저블의 라이프사이클과 컴포즈가 어떻게 컴포저블을 재구성할지 결정하는 것에 대하여 배울 수 있습니다.
Lifecycle Overview
상태 관리 문서(번역문서) 에서 소개한 것 처럼, 구성(Composition)은 UI를 설명하는 컴포저블들을 실행함으로써 앱의 UI를 생성합니다. 구성(Composition)은 UI를 설명한 컴포저블 들의 트리구조 입니다.
젯팩 컴포즈가 컴포저블들을 처음 실행할 때, 즉 초기 구성(initial composition)을 하는 동안, 구성에서 UI를 설명하기 위해 호출하는 컴포저블들을 추척합니다. 그런 다음 젯팩 컴포즈는 만약 앱의 상태가 변경되면 재구성(Recomposition) 작업을 스케쥴링합니다. 재구성은 젯팩 컴포즈가 상태 변경에 따라 변경되었을 수 있는 컴포저블들을 다시 실행한 다음 변경사항을 반영하는 것입니다.
구성은 초기 구성 과정에서 생성되고 재구성 시 업데이트 될 수 있습니다. 구성을 변경하는 것은 오직 재구성 방법 뿐입니다.
중요 : 컴포저블의 수명주기는 다음 이벤트에 의해 정의됩니다 :
구성에 진입했을 때, 0회 이상 재구성 되어졌을 때, 구성이 종료되었을 때.
재구성은 일반적으로 State<T>
객체의 변경에 의해 트리거 됩니다. 컴포즈는 구성안에서 State<T>
요소가 있는 모든 컴포저블을 읽고 추적하며 실행합니다, 그리고 호출된 컴포저블은 건너뛸 수 없습니다.
참고 : 컴포저블의 라이프사이클은 뷰, 액티비티, 프래그먼트의 라이프사이클보다 더 단순합니다. 컴포저블이 수명주기가 더 복잡한 외부 리소스를 관리 또는 상호작용 해야 한다면 effects 를 사용해야 합니다. (effects 문서 링크 참조 내용 확인을 할 수 없습니다.)
만약 컴포저블이 여러번 호출된다면, 여러개의 인스턴스가 구성 안에 배치됩니다. 구성 안에서 각 인스턴스는 그것만의 수명주기를 가지고 있습니다.
Anatomy of a composable in Composition
구성 속 컴포저블의 객체는 콜 사이트(call site)로 식별됩니다. 컴포즈 컴파일러는 각각의 콜 사이트를 별개로 간주합니다. 여러번의 콜 사이트에서 컴포즈가 호출되면 구성 내에서 여러 컴포저블 인스턴스가 생성됩니다.
중요 용어 : 콜 사이트는 컴포저블이 호출되는 소스코드 위치입니다.
이는 구성 에서의 위치, 즉 UI 트리에 영향을 미칩니다.
구성에서 이전 구성 중 호출한 것과 다른 컴포저블을 호출하는 동안, 컴포즈는 호출된 컴포저블과 호출되지 않은 컴포저블을 식별하며, 양쪽 구성에서 호출된 컴포저블에 대해 입력이 변경되지 않았다면 컴포즈는 재구성단계에서는 변경되지 않은 컴포지션 진행은 피합니다.
모든 재구성을 다시 시작하기 보다는, 성공적으로 (구성을) 완료하기 위해 컴포저블 내에서 부가 작용과 관련하여 독자성을 보존하는 것이 결정적인 관련이 있습니다.
아래 예를 생각해 봅시다 :
이 코드 예시에서, 구성 중 LoginScreen 은 LoginError 컴포저블을 조건부 호출하며, LoginInput 컴포저블은 항상 호출합니다. 각각의 호출은 고유의 콜 사이트와 소스코드 위치가 있고, 컴파일러는 이를 고유하게 식별하기 위해 사용합니다.
비록 LoginInput 이 첫 번째 호출에서 두 번째 호출로 바뀌었지만 LoginInput 인스턴스는 재구성을 통해 보존되어집니다. 그리고 LoginInput 은 재구성간에 변경된 매개 변수가 없으므로 컴포즈에서 호출을 건너뜁니다.
Add extra information to help smart recompositions
컴포저블이 여러번 호출되면 구성에도 여러 번 추가됩니다. 만약 동일한 콜 사이트에서부터 여러번 컴포저블이 호출된다면, 컴포즈는 이 컴포저블에 대하여 고유한 식별을 할 수 있는 정보가 없으므로, 인스턴스를 구분하기 위하여 콜 사이트 외에도 실행 순서를 사용합니다. 이러한 행동은 때로 정말 필요하지만, 몇몇 경우 원치 않는 행동을 할 수 있습니다.
위의 예제에서, 컴포즈는 구성에서 인스턴스를 구분하기 콜 사이트 외에도 실행 순서를 추가적으로 이용하여 인스턴스를 구분합니다. 만약 리스트의 맨 밑에 새로운 movie 가 추가되면, 컴포즈는 구성 내 존재하는 인스턴스를 재사용할 수 있습니다. 리스트 내 위치가 변경되지 않았기 때문에, movie 입력은 그 인스턴스들과(=이미 컴포저블 에서 생성한 인스턴스) 동일합니다.
하지만, movies 목록의 맨 위 또는 가운데에 추가되어 변경이 발생하면, 리스트의 모든 입력 파라메터가 변경된 모든 MovieOverview 는 삭제되거나 재정렬하는 재구성의 원인이 됩니다(입력 데이터 — movies 가 변경되면 — 컴포즈에 구성 된 MovieOverview 파라미터가 변경된 모든 요소). 예를 들어, MovieOverview 에서 이미지를 가져오는 부가 작용이 있다면, 이는 매우 중요합니다. 이펙트가 진행되는 동안 재구성이 발생하면 취소되고 다시 시작됩니다.
이상적으로, 우리는 MovieOverview 인스턴스의 아이덴티티와 거기(=MovieOverview 인스턴스)에 전달한 movie 의 아이덴티티가 연결되어 있다고 생각하기를 원합니다. 만약 movie 목록을 재정렬하는 경우, movie 객체로 구성한 각각의 MovieOverview 를 재구성하는 대신 구성 트리 내 객체를 비슷하게 변경할 수 있습니다. 컴포즈에서는 런타임 시 구성 트리 내 요소를 식별 가능하게 알려줄 수 있는, 키(key
) 컴포저블을 제공합니다.
코드 블록을 하나 이상의 값이 전달되는 컴포저블을 키 컴포저블로 감싸서, 이 값들(=컴포저블에 전달하는 값)과 컴포지션의 객체를 결합하여 식별하는데 사용됩니다. 키 값은 전역적으로 고유할 필요가 없으며, 콜 사이트의 컴포저블들을 호출하는 동안만 고유하면 됩니다. 따라서, 이 예제에서는 각 movie 마다 고유한 키가 필요합니다; 그리고 이 키를 앱 내 다른 컴포저블에게 공유해도 괜찮습니다.
위와 같은 경우, 비록 목록 내 요소가 변경되더라도 컴포즈는 MovieOverview 개별 호출한 것을 인식하여 다시 사용할 수 있습니다.
중요 : 구성안에서 Key 컴포저블을 사용하는 것은 컴포즈에게 컴포저블 객체를 식별할 수 있도록 도울 수 있습니다. 이는 부가 작용 또는 내부 상태를 포함하는 동일한 콜 사이트의 컴포저블이 여러번 호출할 때 중요합니다.
몇몇 컴포저블은 키 컴포저블이 내장되어 있습니다. 예를 들어, LazyColumn 에서 items
DSL 항목으로 별도 키를 지정할 수 있습니다.
Skipping if the inputs haven’t changed
구성 내 이미 컴포저블이 있는 경우 모든 입력이 안정적(stable)이고 변경되지 않았다면 재구성을 건너뛸 수 있습니다.
안정적인 유형은 다음 계약을 준수해야 합니다 :
- 두 객체의
equals
의 결과는 두 객체에 대하여 영원히(forever) 동일해야 합니다. - 만약 공용 속성 타입(public property type) 이 변경되면, 구성은 알림을 받습니다.
- 모든 공용 속성 또한 안정적입니다.
컴포즈 컴파일러가 이 계약사항을 지키기 위해 안정적으로 대하는데 있어, 비록 @Stable
어노테이션을 명시적으로 표시하지 않았지만 몇 가지 중요한 일반 타입이 있습니다.
- 모든 기본 타입 (primitive value types) : Boolean, Int, Long, Float, Char, 그 외
- Strings
- 모든 함수 타입 (lambdas)
이러한 타입 들은 모두 불변이기 때문에 안정성을 따를 수 있습니다. 불변 타입은 절때 변경되지 않기 때문에, 구성에 변경사항을 알릴 필요가 없고, 따라서 이러한 계약을 따르기 훨씬 쉽습니다.
참조 : 깊게 불변하는(deeply immutable) 모든 타입은 안전하게 안정적인 타입으로 여겨집니다.
안정적이지만 가변(mutable)인 컴포즈의 MutableState
은 주목할만한 타입입니다. 만약 MutableState 가 값을 유지하고 있고, State
의 .value
프로퍼티가 어떠한 변경사항 생긴다면 상태 객체를 컴포즈에게 알리기 때문에 전체적으로 안정적으로 간주됩니다.
컴포저블에 파라미터로 전달한 모든 타입이 안정적이면, 파라미터의 값은 UI 트리상 컴포저블 위치를 기준으로 동일성에 대해 비교합니다. 이전 호출 이 후 모든 값이 변경되지 않은 경우 재구성을 건너뜁니다.
중요 : 모든 입력 값이 안정적이고 변경사항이 없다면 컴포즈는 컴포저블의 재구성을 건너뜁니다. 비교 시 equals 함수를 사용합니다.
컴포즈는 그것을 증명할 수 있어야만 안정적인 타입으로 간주합니다. 예를들어, 인터페이스는 일반적으로 안정적으로 간주하지 않으며, 가변의 공용 속성(public properties)을 가진 구현체가 불변일 수 있는 타입을 가지고 있는 것 또한 안정적이지 않습니다.
컴포즈가 타입이 안정적이라는것을 추론할 없지만, 컴포즈에게 안정적으로 취급될 수 있도록 @Stable
어노테이션을 표시합니다.
위의 코드에서, UiState 는 인터페이스 이므로 컴포즈는 보통 이 타입을 안정적으로 간주하지 않습니다. @Stable 어노테이션을 추가하여, 컴포즈에게 이 타입이 안정적이라는 것을 알림으로써, 컴포즈가 영리하게 재구성하는 것을 도울 수 있습니다. 이는 컴포즈는 만약 인터페이스가 파라미터 타입으로 사용된다면 이 인터페이스의 모든 구현을 안정적으로 간주한다는 의미이기도 합니다.
중요 : 만약 컴포즈가 타입에 대하여 안정성을 유추할 수 없는 경우, 타입에 @Stable 어노테이션을 달아서 컴포즈가 영리하게 재구성 하도록 허용해야 합니다.