Jetpack Compose RememberObserver 소개

컴포즈 SideEffect handler 의 핵심 구현

Ji Sungbin
성빈랜드
6 min readJul 14, 2022

--

Photo by sanjiv nayak on Unsplash

Jetpack Compose 에는 SideEffect handler 를 구현하기 위해 RememberObserver 라는 인터페이스가 있습니다.

이 인터페이스를 구현하는 개체는 컴포지션에 처음 사용되었을 때와 더 이상 사용되지 않을 때 알림을 받을 수 있습니다. 즉, 이 인터페이스를 구현하여 LaunchedEffect 과 DisposableEffect 이 구현됩니다.

RememberObserver 는 3개의 함수를 갖고 있습니다.

  • onRemembered: 컴포지션에 의해 성공적으로 사용될 때 호출됩니다. -> 이 값을 사용했음을 기억시킴, remember 상태
  • onForgotten: 컴포지션에 의해 사라질 때 호출됩니다. -> 아예 사라지므로 이 값을 사용한지 모름, forget 상태
  • onAbandoned: 컴포지션에게 remember 를 요청됐지만 실패했을 때 호출됩니다.​

remember 컴포저블 함수와 완전히 다른 개념입니다. remember 컴포저블은 값을 슬릇 테이블에 저장하여 캐싱하는 반면, RememberObserver 에서 remember 은 컴포저블이 이 값을 사용했다는 것을 기억하는 여부를 뜻 합니다.

이 인터페이스를 구현한 객체가 함께 remember 되고 forget 될 때 onForgotten 의 순서는 onRemembered 의 반대 순서로 호출되는 것이 보장됩니다. 예를 들어, 두 개의 객체 A 와 B 가 순서대로 remember 되면 onRemembered 는 A -> B 순서로 호출됩니다. 같이 forget 된 경우 onForgotten 은 B -> A 순서로 호출됩니다.​

이 인터페이스가 사용되는 시나리오는 아래와 같습니다.​

  1. onRemembered 또는 onAbandoned 가 호출됩니다.
  2. onRemembered 가 호출되면 onForgotten 이 결국 호출됩니다.

이제 어떻게 쓰이고 있는지 확인해 보겠습니다.

우리가 가장 많이 쓰는 SideEffect handler 인 LaunchedEffect 함수는 단 2줄로 구현돼 있습니다. 우선 composer 에서 현재 CoroutineContext 를 가져와서 LaunchedEffectImpl 로 넘기고 있고, 이렇게 만들어지는 LaunchedEffectImpl 을 remember 로 기억하고 있습니다.

여기에 쓰인 remember 가 무슨 역할을 하는지는 추후 알아보고, LaunchedEffectImpl 을 보겠습니다.

RememberObserver 를 상속하고 onRemembered 에서 task 를 Job 으로 받아서 실행하고 있습니다. 이 Job 은 onForgotten 과 onAbandoned 에서 취소됩니다.

LaunchedEffect 은 이렇게 아주 간단한 동작입니다. DisposableEffect 도 똑같은 방식으로 구현됐습니다.

DisposableEffect 의 effect 는 onRemembered 에서 실행하고, dispose 는 onForgetten 에서 실행하고 있는걸 볼 수 있습니다.

RememberObserver 의 대표적인 사례로는 SideEffect handler 의외에 rememberCoroutineScope 도 있습니다.

코루틴 스코프를 만들어서 CompositionScopedCoroutineScopeCanceller 로 래핑한 다음, onForgotten 과 onAbandoned 에서 해당 스코프를 cancel 해주고 있습니다. 이런 원리로 rememberCoroutineScope 가 안전하게 됩니다.

지금까지 RememberObserver 에 대해 보았습니다. 그렇다면 이 RememberObverber 들은 언제 호출될까요? 정답은 RememberEventDispatcher 에 있습니다.

여기부터 “Jetpack Compose 데이터 저장 시스템” 내용이 사용됩니다. 아직 읽지 않으신 분들은 읽어주세요.

RememberManager 은 RememberObserver 들을 저장하고 삭제하는 등 관리하기 위한 인터페이스고, RememberManager 구현과 동시에 RememberObserver 호출은 RememberEventDispatcher 에서 해주고 있습니다.

RememberEventDispatcher 을 보면 remembering, forgetting 으로 RememberObserver 를 관리하고 있고, dispatchRememberObservers 로 호출하고 있습니다. 덤으로 sideEffect 과 dispatchSideEffects 으로 SideEffect 까지 관리하고 있는걸 확인할 수 있습니다. SideEffect 은 말 그대로 부수효과 이기 때문에 슬릇 테이블에 저장되지 않습니다.

따라서 이렇게 람다 원형으로 바로 저장하고 있으며, RememberObserver 에 의해 관리되지 않습니다.

이제 아까 보았던 LaunchedEffect 구현에 사용된 remember 을 알아보기 좋은 시점 입니다.

remember 의 구현을 먼저 보겠습니다. 이 글은 RememberObserver 에 대한 글이므로 구현 세부 사항은 생략하겠습니다.

remember 는 여러 로직을 거치면서 결국 값 저장을 위해 updateValue 를 호출합니다. updateValue 는 슬릇 테이블에 값을 업데이트 하는 함수이고, 이 곳에서 RememberEventDispatcher 가 사용됩니다.

15번 라인과 25번 라인을 보면 새로운 값이 저장될 때 만약 해당 값이 RememberObserver 를 상속받고 있다면 remembering 해주고 있고, 30번 라인을 보면 만약 새로운 값으로 업데이트 되기 전 기존 값이 RememberObserver 를 상속받고 있다면 forgetting 해주고 있는걸 볼 수 있습니다.

이렇게 매번 값 업데이트가 필요할 때마다 RememberObserver 가 Change 로 기록되고, 이렇게 등록된 Change 를 반영과 동시에 RememberObserver 실행은 applyChanges() 에서 해주고 있습니다.

이 함수는 매 컴포지션 마다 실행되며, 7~13번 줄을 통해 Change 가 반영되고 17~18번 줄을 통해 RememberObserver 를 호출하고 있습니다.

끝!

이렇게 해서 RememberObserver 가 구현되고 사용됩니다. RememberObserver 는 public 이기 때문에 원한다면 직접 구현할 수도 있습니다. 하지만 이미 LaunchedEffect 과 DisposableEffect 으로 구현돼 있기 때문에 직접 구현할 일은 없을거 같습니다. 읽어주셔서 감사합니다.

[목차로 돌아가기]

안드로이드 개발자 분들을 위한 카카오톡 오픈 채팅방을 운영하고 있습니다.

--

--

Ji Sungbin
성빈랜드

Experience Engineers for us. I love development that creates references.