[Android] LiveData와 Flow, 그리고 StateFlow


개요

MVVM 아키텍쳐를 설계할 때 ViewModel에서 사용되는 관찰 가능한 데이터로써 흔히 AAC에서 제공하는 LiveData를 사용합니다. 그런데 이때 LiveData 대신 코루틴에서 지원하는 Flow라는 대체제가 존재합니다. 하지만 이 둘은 차이가 있기 때문에 서로를 완전히 대체할 수는 없습니다. 그렇다면 같이 사용하는 방법은 있을까요? 또 그랬을 때의 이점은 무엇일까요? 둘 말고 하나만 사용하는 법은 없을까요? 한 번 알아봅시다.


LiveData

LiveData는 관찰 가능한 데이터 홀더 타입로써 AAC의 ViewModel이나 DataBinding과 호환되어 MVVM 패턴을 구현하기 위해 적극적으로 사용되고 있습니다. 뿐만 아니라 Retrofit이나 Room에서도 LiveData를 지원하기 때문에 Data Layer에서도 활용할 수 있습니다.

이때 LiveData의 가장 큰 특징은 바로 안드로이드의 수명주기를 인식한다는 것입니다. 이를 통해 LiveData를 사용하여 UI를 갱신하면 메모리 누수가 없고, 중지된 컴포넌트로부터 앱이 튕기는 일을 방지할 수 있습니다.

하지만 LiveData에도 한계점이 있습니다. 바로 비동기 데이터 스트림을 지원하지 않는다는 것입니다. 그 이유는 LiveData는 UI와 밀접하게 연관되어 있기 때문에 오직 Main Thread에서만 관찰되기 때문입니다. 이러한 한계는 ViewModel을 통해 View를 업데이트할 때에는 문제가 되지 않습니다만, Data Layer에서 사용 시 문제가 됩니다. 데이터를 받아오고 처리하는 과정은 Main Thread가 아닌 Work Thread에서 처리해야 성능 상 유리하기 때문입니다.

Clean Architecture 관점에서 조금 더 살펴보면 Domain Layer에서도 사용하기 부적합합니다. 그 이유는 Domain Layer는 플랫폼에 종속적이지 않은 순수한 Kotlin 및 Java 코드로 이루어져야 하는데, LiveData는 안드로이드 플랫폼에 종속적이기 때문입니다.

그런데 순수 Kotlin에서 지원하는 클래스 중 관찰 가능한 데이터 홀더 타입임과 동시에 비동기 데이터 스트림을 지원하는 녀석이 존재합니다.


Flow

바로 Flow가 그 녀석입니다.

그렇다면 Flow는 LiveData를 완전히 대체할 수 있을까요? 그렇지는 않습니다. 바로 다음과 같은 부분에서 LiveData와 차이점을 갖습니다.

Flow의 한계점

  • Flow는 스스로 안드로이드 생명주기에 대해 알 수 없다.
  • Flow에는 상태 또는 값이 없다.
  • Flow는 cold stream 방식으로 연속해서 들어오는 데이터를 처리할 수 없으며 collect 되었을 때만 생성되고 값을 반환한다. 이때 collector 하나 당 새로운 Flow가 생성된다. 따라서 DB 접근 또는 API 통신 시에 collect 할 때마다 중복해서 리소스 요청을 수행한다.

이러한 부분에서 역시 LiveData를 완전히 대체하긴 어려워 보이지만 여전히 Data Layer에서 Flow를 사용하는 것은 유리해보입니다. 심지어 LiveData와 마찬가지로 Retrofit이나 Room에서 Flow를 지원합니다.

이쯤되니 서로의 한계점을 보완할 수 있는 최적의 방안이 머릿속에 하나 떠오릅니다.


LiveData + Flow

0

그렇다면 위와 같이 ViewModel에서는 LiveData를 사용하고 Repository와 Data Source 영역에서는 비동기적으로 Flow를 사용하면 되지 않을까요? 실제로 위와 같은 구조는 구현이 가능한 구조입니다. 바로 다음과 같은 기능들이 지원되기 때문입니다.

  • RetrofitRoom이 코루틴(suspend)를 지원한다.
  • 이를 repository에서 Flow로 collect 할 수 있다.
  • Flow를 asLiveData()를 통해 LiveData로 변환할 수 있다.

즉 정리하면, Room이나 Retrofit에서 코루틴의 suspend 키워드로 비동기 인터페이스를 구현하고, Repository에서 Data Source로부터 Flow를 받아온 뒤 ViewModel에서 이를 LiveData로 변환하여 View에서 관찰하게 하는 구조입니다.

안드로이드 디벨로퍼 사이트에서도 위와 같이 Repository에서는 LiveData 대신 Flow를 사용할 것을 권장하고 있습니다.


StateFlow의 등장

위와 같이 LiveData와 Flow를 같이 사용하여 서로를 보완할 수 있었습니다. 그런데 Kotlin의 Flow API에서 StateFlowSharedFlow가 등장하였습니다. StateFlow는 SharedFlow의 한 종류로서, 기존의 Flow에 비해 다음과 같은 특징을 갖습니다.

Flow와의 차이점

  • 항상 값을 갖고 있고, 오직 하나의 값을 갖는다.
  • Hot stream 방식으로 collector가 없어도 생성 시 바로 활성화되며, 값이 업데이트 된 경우에만 반환한다.
  • 여러 개의 collector를 지원하기 때문에 중복 리소스 요청을 방지한다.

위 차이점을 살펴보면 Flow가 LiveData를 완벽히 대체할 수 없었던 문제들을 모두 해결하고 있음을 알 수 있습니다. 안드로이드의 생명주기를 직접 알 수 없다는 문제 또한 LifeCycleScope를 확장하여 해결이 가능합니다. 실제로 StateFlow는 LiveData와 MVVM 아키텍쳐에서 거의 동일한 패턴을 따릅니다. 또 Android Studio Arctic fox 버전부터는 StateFlow가 AAC DataBinding을 지원하기 시작하면서 LiveData를 완전히 대체할 수 있다는 의견이 나오게 되었습니다.

Reactive Programming 관점에서 살펴보면 기존의 Flow에 비해 기능이 추가되었고 Rx와 더욱 유사해지면서 Coroutines + StateFlow 조합으로 RxJava 등과 같은 기존의 방식들과 어께를 나란히 하게 되었습니다.

StateFlow로 실시간 UI 업데이트

1

실제로 위와 같은 구조로 RxJava와 동일한 기능을 수행할 수 있습니다. Data Source에서 suspend 키워드를 통해 비동기로 데이터를 반환하는 것이 아니라, Flow 객체를 직접 반환한 뒤 repository 레이어에서 collect 작업을 통해 데이터 요청 상태를 반환합니다. 그리고 ViewModel에서 StateFlow를 통해 repository의 flow로 데이터를 collect 합니다. 이때 StateFlow는 위에서 언급했다시피 hot stream 방식이기 때문에 다중 collector 사용이 가능합니다. 최종적으로 View에서 이를 관찰하게 하면, View에서 데이터의 로딩, 업데이트, 요청 실패 등등에 따라 UI를 자동으로 업데이트할 수 있습니다.

이와 같은 작업이 가능하기 때문에 최근에는 더 이상 LiveData와 Flow를 같이 사용함으로써 서로를 보완하는 것이 아니라 StateFlow 하나로 완전히 대체하려는 시도가 나타나게 되었고, 현재는 안드로이드 디벨로퍼에서 최신 예제들은 모두 StateFlow를 이용하고 작성되고 있습니다.


마치며

LiveData만을 사용했을 때의 한계점을 Flow를 같이 사용함으로써 해결하는 과정과, 최근에 등장한 StateFlow로 모든 것을 대체할 가능성에 대해 살펴보았습니다. 최근 들어 기업에서도 Rx 대신 코루틴을 이용하는 경우가 많아지고 있고, 이제는 안드로이드 진영에서 이 방법이 정석으로 자리잡게 될 것이라고 생각합니다.

0%