collectAsStateWithLifecycle은 어떻게 작동하는걸까?

Juho Park
8 min readJul 8, 2024

--

Photo by Steve Johnson on Unsplash

collectAsStateWithLifecycle의 작동원리를 알아보고
collectAsState와 비교해봅니다.

19년 8월, Kotlin의 Flow가 릴리즈되고 현재까지 많은 개발자들에게 사랑받고 있습니다. Android에서는 Flow를 상속하는 SharedFlow, StateFlow를 통해 LiveData의 단점이었던 Android 종속성을 덜어내고 이벤트/데이터 관리를 손쉽게 처리할 수 있습니다.

그리고 과거에 Android UI 개발을 담당했던 XML에 이어 현재는 Jetpack Compose라는 UI 빌드 도구 키트가 활발하게 사용중인데요, Compose에서 Flow를 잘 사용할 수 있도록 collectAsStateWithLifecycle 라는 Extention을 제공해주고 있습니다.

collectAsStateWithLifecycle의 사용법과 작동원리에 대해 분석해보고 왜 Android 플랫폼에선 collectAsState가 아닌 collectAsStateWithLifecycle를 사용해야하는지 알아보겠습니다.

Sunflow 프로젝트 코드를 보며 어떤식으로 Flow가 활용되었는지,
내부 코드는 어떻게 구성되고 의도되었는지 살펴봅니다.

collectAsState에서 collectAsStateWithLifecycle로 적용한
Commit을 보려면 PR 을 확인해주세요.
https://github.com/android/sunflower/pull/976

예시보려 들어간 프로젝트에 적용이 안되어있어 변경 후 Contributor가 되었다😁

collectAsStateWithLifecycle란?

자세한 내용에 앞서 collectAsStateWithLifecycle에 기능에 대해 알아보겠습니다. collectAsStateWithLifecycle는 Android에서 사용되는 확장 함수로, Jetpack Compose와 함께 사용되어 State 객체를 Lifecyclescope 안에서 안전하게 수집할 수 있도록 합니다. 이 함수는 Flow를 Compose UI에서 상태(State)로 수집할 때 사용되며, 컴포넌트의 생명주기를 고려하여 자동으로 수집을 중단하거나 재개합니다. 이를 통해 불필요한 자원 낭비를 방지하고, 메모리 누수를 예방할 수 있습니다.

사용법

많은 포스팅에서 다룰 당시에는 알파 단계에서 제공하던 기능이었으나 현재는 정식 버전에서 collectAsStateWithLifecycle를 제공하고 있습니다.

예시 및 내부 구조

Android Sunflower 프로젝트는 MVVM 구조로 UI Layer가 Data Layer를 바라보는 형태로 이루어져 있습니다. 아키텍처 구조가 낯설다면 앱 아키텍처 가이드를 살펴본 뒤 포스팅을 보는 것을 추천드려요.

예시로 들 레이어 구조

화살표를 바라보는 방향에서 데이터를 요청하고 결과값을 돌려주는 형태입니다. 차근차근 코드를 알아볼게요.

오늘의 핵심 코드입니다. 구조상 UI Layer에 해당하는 코드로 ViewModel의 plantAndGardenPlantings 변수를 collectAsStateWithLifecycle() 확장함수롤 호출하며 by 로 위임해 gardenPlants 라는 변수를 생성한걸 볼 수 있습니다. plantAndGardenPlantings를 호출한 뷰모델을 이어서 확인해보겠습니다.

plantAndGardenPlantings는 StateFlow로 Hilt을 통해 주입받은 gardenPlantingRepository에서 정보를 불러오는 것을 알 수 있습니다.

stateIn 함수는 Flow 자료형을 StateFlow로 바꿔주는 함수입니다.

기본적으로 Flow는 ColdStream이며 StateFlow는 HotStream이므로 어느 시점부터 관찰을 시작할지 선택해야 합니다. 해당 예시에서는 화면이 올라와있는 시점에만 관찰을 할 것이므로 SharingStarted.WhileSubscribed 옵션을 적용하였습니다.

또한 Configuration 등의 이벤트로 Recomposition이 일어날 경우 collect이 다시 시작하는 것을 방지하기 위해 WhileSubscribe를 5초로 지정하였습니다. UpStream 흐름을 5초간 유지함으로써 발생할 수 있는 비용을 줄일 수 있습니다.

다음은 gardenPlantingRepository.getPlantedGardens() 을 확인해봅니다.

Room 라이브러리를 이용해 Flow<List<PlantAndGardenPlantings>> 데이터를 쿼리하여 반환하네요.

예제로 본 코드를 다음과 같이 정리할 수 있습니다.

  1. UI Layer에서 데이터를 요청한다.
  2. Data Layer에서 데이터를 반환한다. (Flow)
  3. UI Layer에서는 전달받은 데이터를 변환하여 생명주기에 맞게 UI에 표시한다. (Flow -> StateFlow)

우리는 전달받은 Flow를 StateFlow로 변환하지 않고도 사용할 수 있습니다.

stateIn 함수를 사용하지 않고 Flow를 유지하면서

UI Layer에서 관찰할 수도 있습니다.

다만, 이렇게 코드를 작성하면 발생하는 문제가 있습니다.

collectAsState vs collectAsStateWithLifecyle

백그라운드 이동 시 생명주기에 맞추어 collection이 중지되지 않고 리소스가 낭비되게 됩니다.

collectAsState 내부 소스코드를 확인해보면 LifeCycle을 확인하지 않고 값을 단순히 collect합니다.

그렇다면 collectAsStateWithLifecycle 는 어떻게 되어있을까요?

기본적으로 lifecycleOwner와 ActiveState로 취급할 상태 값을 파라미터로 전달받고 있으며, 이후 똑같은 이름의 collectAsStateWithLifecycle 을 overload하여 내부 호출합니다.

produceState를 통해 람다식에서 생명주기에 맞춰 collect할 수 있도록 repeatOnLifecycle 을 호출합니다. repeatOnLifecycle 는 Compose를 사용하기 이전에도 Flow 사용 시 보일러플레이트 코드를 줄이기 위해 사용한 함수입니다.

이를 통해 화면이 활성화되는 상황에만 데이터를 수집할 수 있습니다.

1 Depth 더 깊이 들어가봅시다.

produceState를 통해 전달받은 최초 값을 return하고 있으며 LaunchedEffect를 통해 최초 혹은 key 중 값의 변화가 있을 때마다 ProduceStateScopeImplProduceStateScope.producer() 를 호출하고 결과적으로 람다식을 호출하게 되어 repeatOnLifecycle 가 실행되는 구조입니다.

그렇다면 collectAsState 는 언제 호출되나요?

collectAsState는 Android보다는 KMP 등을 활용한 멀티 플랫폼에서 활용할 수 있을 것으로 생각이됩니다.

Data Layer에서 Android 종속성을 걷어낸 Flow를 그대로 사용하면서 Android에서는 생명주기를 참조하는 collectAsStateWithLifecycle를, 생명주기를 상관하지 않는 플랫폼에서는 collectAsState 를 사용하면 최소한의 수정으로 멀티 플랫폼 개발을 하는 식으로요!

글을 마치며

collectAsStateWithLifecycle 에 대해 알아보면서 간단하지만 강력한 Extention이 어떤식으로 작동하는지를 알아보았습니다. 최근 사내에서 Compose를 도입하며 개인적으로 공부중인데, 배운 것을 통해 Android Sunflower 프로젝트에 Contributor도 되어보고 블로그 후기를 쓸 수 있어 더욱 뜻깊고 재밌는 시간이었습니다.

잘못된 내용이나 더 좋은 정보가 있다면 댓글 부탁드려요 :)

읽어주셔서 감사합니다!

참고

--

--