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
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>>
데이터를 쿼리하여 반환하네요.
예제로 본 코드를 다음과 같이 정리할 수 있습니다.
- UI Layer에서 데이터를 요청한다.
- Data Layer에서 데이터를 반환한다. (Flow)
- UI Layer에서는 전달받은 데이터를 변환하여 생명주기에 맞게 UI에 표시한다. (Flow -> StateFlow)
우리는 전달받은 Flow를 StateFlow로 변환하지 않고도 사용할 수 있습니다.
stateIn
함수를 사용하지 않고 Flow를 유지하면서
UI Layer에서 관찰할 수도 있습니다.
다만, 이렇게 코드를 작성하면 발생하는 문제가 있습니다.
백그라운드 이동 시 생명주기에 맞추어 collection이 중지되지 않고 리소스가 낭비되게 됩니다.
collectAsState
내부 소스코드를 확인해보면 LifeCycle을 확인하지 않고 값을 단순히 collect합니다.
그렇다면 collectAsStateWithLifecycle
는 어떻게 되어있을까요?
기본적으로 lifecycleOwner와 ActiveState로 취급할 상태 값을 파라미터로 전달받고 있으며, 이후 똑같은 이름의 collectAsStateWithLifecycle
을 overload하여 내부 호출합니다.
produceState를 통해 람다식에서 생명주기에 맞춰 collect할 수 있도록 repeatOnLifecycle
을 호출합니다. repeatOnLifecycle
는 Compose를 사용하기 이전에도 Flow 사용 시 보일러플레이트 코드를 줄이기 위해 사용한 함수입니다.
이를 통해 화면이 활성화되는 상황에만 데이터를 수집할 수 있습니다.
1 Depth 더 깊이 들어가봅시다.
produceState를 통해 전달받은 최초 값을 return하고 있으며 LaunchedEffect
를 통해 최초 혹은 key 중 값의 변화가 있을 때마다 ProduceStateScopeImpl
의 ProduceStateScope.producer()
를 호출하고 결과적으로 람다식을 호출하게 되어 repeatOnLifecycle
가 실행되는 구조입니다.
그렇다면 collectAsState
는 언제 호출되나요?
collectAsState
는 Android보다는 KMP 등을 활용한 멀티 플랫폼에서 활용할 수 있을 것으로 생각이됩니다.
Data Layer에서 Android 종속성을 걷어낸 Flow를 그대로 사용하면서 Android에서는 생명주기를 참조하는 collectAsStateWithLifecycle
를, 생명주기를 상관하지 않는 플랫폼에서는 collectAsState
를 사용하면 최소한의 수정으로 멀티 플랫폼 개발을 하는 식으로요!
글을 마치며
collectAsStateWithLifecycle
에 대해 알아보면서 간단하지만 강력한 Extention이 어떤식으로 작동하는지를 알아보았습니다. 최근 사내에서 Compose를 도입하며 개인적으로 공부중인데, 배운 것을 통해 Android Sunflower 프로젝트에 Contributor도 되어보고 블로그 후기를 쓸 수 있어 더욱 뜻깊고 재밌는 시간이었습니다.
잘못된 내용이나 더 좋은 정보가 있다면 댓글 부탁드려요 :)
읽어주셔서 감사합니다!