Composition

  • WHAT: Compose에서 상태 기반 UI를 구성하고 재구성하는 단위이다.
  • WHO: ViewGroup, Recomposer, Composer, Applier, RememberObserver 등이 관여한다.
  • WHEN: setContent 호출 시 생성되고, 상태 변경 시 재구성된다.
  • WHERE: AndroidComposeView 내부에서 동작하며 Compose Runtime이 이를 관리한다.
  • WHY: 상태 변경에 따라 UI를 자동으로 갱신하고 효율적으로 처리하기 위해 필요하다.
  • HOW: Composable 실행 → 상태 스냅샷 → 변경사항 적용 → 생명주기 처리 흐름으로 작동한다.

생성하기


graph TD
  A[ViewGroup.setContent 호출] --> B[doSetContent 실행]
  B --> C[기존 Composition 확인]
  C -->|존재하고 유효| D[재사용]
  C -->|없거나 disposed| E[새 Composition 생성]
  E --> F[WrappedComposition 생성]
  F --> G[CompositionLocal 설정: Context, Lifecycle 등]
  D --> H["setContent(content) 실행"]
  G --> H

  N[Composition 생성 시] --> O[parent CompositionContext 전달 가능]
  O --> P[CompositionLocal 및 Invalidation 연결]

  N --> Q[CoroutineContext 지정 가능]
  Q --> R["기본값: EmptyCoroutineContext (Main에서 실행)"]

  H --> S["UI 종료 시 composition.dispose() 호출"]

ViewGroup.setContent

  • Android에서는 ViewGroup.setContent를 통해 새로운 Composition이 생성됨.
  • 내부적으로 doSetContent가 호출되어 AndroidComposeView와 parent CompositionContext, content(@Composable)를 전달함.
  • 기존 Composition이 있는 경우 재사용하고, 없거나 disposed 상태이면 새로운 Composition을 생성함.
  • 최종적으로 composition.setContent(content)를 호출해 내용을 설정함.

WrappedComposition

  • Composition과 AndroidComposeView를 연결해주는 데코레이터 역할.
  • 키보드 상태, 접근성, Context 등의 정보들을 CompositionLocal로 전달함.
  • 이를 통해 Composable 함수들이 Context 정보에 접근 가능하게 됨.
  • root LayoutNode에 대한 UiApplier 인스턴스를 Composition에 전달함.

예시


graph TD
  I[VectorPainter 내부] --> J[composeVector 사용 → Composition 생성]
  J --> K[DisposableEffect로 dispose 관리]
  L[SubcomposeLayout] --> M[측정 중 Composition 유지 및 재구성]

VectorPainter

  • VectorPainter 내부에서도 자신의 Composition을 관리함.
  • composeVector를 통해 Composition을 만들고, content를 설정함.
  • DisposableEffect를 통해 Composition이 더 이상 필요 없을 때 dispose 처리함.

SubcomposeLayout

  • Layout 중 자신의 Composition을 가지는 컴포넌트.
  • 측정 단계에서 자식 요소의 측정을 위해 별도로 구성(subcompose) 가능함.

CompositionContext

  • Composition 생성 시 parent CompositionContext를 함께 전달할 수 있음.
  • null이 아니면 기존 Composition과 논리적으로 연결되어 CompositionLocal이나 invalidation이 이어질 수 있음.

Recompose Context

  • Composition 생성 시 CoroutineContext를 지정할 수 있음.
  • 기본적으로는 Recomposer가 제공하는 EmptyCoroutineContext를 사용.
  • Android에서는 기본적으로 AndroidUiDispatcher.Main에서 recomposition이 발생함.

Composition의 소멸

  • 더 이상 필요하지 않으면 composition.dispose()를 호출해 정리함.
  • Composition은 소유자(owner)에 종속되며, 소유자가 사라지면 Composition도 같이 사라져야 함.

초기 Composition 과정

composition.setContent(content)의 동작

  • Composition이 처음 만들어질 때 항상 호출됨.
  • parent Composition을 통해 composeInitial(this, content)가 실행됨.
  • parent는 다른 Composition이거나, 루트 Composition의 경우 Recomposer가 됨.

composeInitial 내부 흐름

  • 현재 State 객체들의 값을 스냅샷(snapshot)으로 복사함.
  • snapshot은 mutable하며 thread-safe함.
  • 변경 감지를 위해 snapshot에는 State 읽기/쓰기 관찰자가 함께 설정됨.
  • snapshot.enter { composition.composeContent(content) } 호출로 실제 content가 구성됨.
  • snapshot이 끝나면 변경된 내용을 전역 shared state로 적용하기 위해 snapshot.apply() 호출.

실제 Composition 과정 (Composer에게 위임됨)

  • Composition이 이미 실행 중이면 예외 발생, 중첩 실행 불가.
  • pending invalidation이 있다면, 이를 가져와 Composer의 리스트에 추가함.
  • isComposing 플래그를 true로 설정.
  • startRoot() 호출로 Composition의 루트 그룹 시작.
  • startGroup()으로 content 그룹 시작.
  • content 람다 실행 (Composable 함수 실행).
  • endGroup()으로 content 그룹 종료.
  • endRoot() 호출로 Composition 종료.
  • isComposing을 false로 되돌림.
  • 임시 데이터를 유지하던 구조들 정리.

초기 Composition 후 변경 사항 적용

composition.applyChanges()

  • 초기 Composition 후, Applier에게 변경 사항 적용을 지시함.
  • Composition이 applier.onBeginChanges()를 호출해 변경 준비를 시작함.
  • 기록된 변경 목록을 순회하며 각 변경을 Applier와 SlotWriter를 통해 적용함.
  • 모든 변경 적용 후, applier.onEndChanges()를 호출하여 완료를 알림.

RememberedObservers 알림

  • Composition에 등록된 모든 RememberObserver 구현 클래스에 대해 진입/이탈 이벤트를 전달함.
  • 예: LaunchedEffect, DisposableEffect 등은 Composition의 생명주기에 맞춰 동작하도록 구현되어 있음.

SideEffect 실행

  • 모든 SideEffect는 기록된 순서대로 실행됨.

Composition에 대한 추가 정보

Composition의 상태 인식

  • Composition은 자신의 재구성이 필요한 상태(Invalidation) 를 인지할 수 있음.
  • 현재 컴포지션 중인지 여부도 알고 있음.
  • 이 정보는:
    • 컴포지션 중일 때 즉시 invalidation을 적용하거나,
    • 그렇지 않으면 나중으로 연기 가능하게 함.
  • Recomposer는 이 정보를 바탕으로 불필요한 재구성을 생략할 수 있음.

ControlledComposition

  • Composition의 변형으로, 외부에서 제어할 수 있도록 몇 가지 함수가 추가됨.
  • 예시: composeContent, recompose 등.
  • Recomposer는 이들을 사용해 컴포지션에 재구성이나 업데이트를 지시할 수 있음.

Composition의 관찰 기능

  • Composition은 자신이 특정 객체들을 관찰 중인지 확인 가능.
  • 이를 통해 해당 객체가 변경될 때 재구성이 필요함을 감지함.
  • 예: 부모 Composition에서 CompositionLocal이 바뀌면, 자식 Composition에 재구성을 유도.
  • Composition 간에는 CompositionContext를 통해 연결되어 있음.

오류 처리

  • 컴포지션 중 오류가 발생하면 중단(abort) 가능.
    • Composer와 그 내부 상태(스택, 참조 등)를 초기화함.

스마트한 재구성

다음 조건이 만족되면 재구성을 건너뜀(skipping):

  • 삽입도 재사용도 하지 않음
  • 무효화된 provider 없음
  • 현재 RecomposeScope에서 재구성이 요구되지 않음