본문 바로가기

Android(Kotlin) Study A부터 Z까지 등반

아키텍처 관점에서 LiveData에 대해서. (StateFlow)

LiveData란?

💡 LiveData란 데이터를 관찰할 수 있는 data holder class 입니다.

  • LifeCycle에 결합되며, 결합된 컴포넌트의 생명주기 상태가 active(STARTED, RESUME)활성화 상태일 때만 data에 대한 update를 제공
  • Observer 객체와 함께 사용한다. LiveData는 변화를 옵저버에게 알려주고 Observer의 onChanged() 메소드를 실행한다.

LiveData 특징

  1. UI가 데이터 상태와 일치하는 것을 보장합니다. LiveData는 Observer 패턴을 따릅니다. LiveData는 생명주기에 변경이 일어날 때마다 Observer 객체에 알려줍니다. 그리고 이 Observer 객체를 사용하면 데이터의 변화가 일어나는 곳마다 일일이 UI를 변경하는 코드를 넣을 필요 없이, 통합적이고 확실하게 데이터의 상태와 UI를 일치시킬 수 있습니다.
  2. 메모리 누수가 없습니다. 옵저버 객체는 Lifecycle 객체와 결합되어 있어서 컴포넌트가 Destroy 될 경우 메모리를 스스로 해제합니다.
  3. Stop 상태의 액티비티와 Crash가 발생하지 않습니다. 액티비티가 Back Stack에 있는 것처럼 Observer의 생명주기가 inactive(비활성화) 일 경우, Observer는 LiveData의 어떤 이벤트도 수신하지 않습니다.
  4. 생명주기에 대한 추가적인 handling을 하지 않아도 됩니다. LiveData가 생명주기에 따른 Observing을 자동으로 관리를 해주기 때문에 UI 컴포넌트는 그저 관련 있는 데이터를 관찰하기만 하면 됩니다.
  5. 항상 최신 데이터를 유지합니다.
  6. 화면 구성이 변경되어도 데이터를 유지합니다. 예를 들어, 디바이스를 회전하여 세로에서 가로로 화면이 변경될 경우에도 LiveData는 회전하기 전의 최신 상태를 즉시 받아옵니다.
  7. Resource를 공유할 수 있습니다. LiveData를 확장하는 클래스를 만들어 싱글톤 패턴으로 관리를 할 경우 앱 전체에서 사용 가능한 LiveData 객체를 만들 수 있고, 이를 통해 자원을 공유할 수 있습니다.

그래서 이걸 왜 항상 ViewModel에 넣는걸까?

보통은 액티비티, 프래그먼트간의 데이터 결합도를 낮추고, UI Controller는 오직 데이터만 띄우는것을 책임지기 위해서

클린 아키텍처에 대한 여러 케이스들

https://proandroiddev.com/multiple-ways-of-defining-clean-architecture-layers-bbb70afa5d4a

Android 관점에서의 Clean Architecture

  • 간단하게 설명하자면 클린 아키텍처에서 계층 간의 의존성은 한쪽으로만 발생하여야 한다. 예를들어, Presentation 계층에서는 Domain 계층을 알지만, 그 반대인 Domain 계층은 Presentation 계층을 알면 안 된다.
  • 한 가지 알아두면 좋은 점은 안드로이드 개발에 자주 사용되는 디자인 패턴인 MVVM 패턴이나 MVP 패턴 등은 Presentation Layer 에 포함되는 패턴. 따라서 MVVM 과 클린 아키텍처는 양자택일의 문제가 아니라 함께 적용이 가능한 부분인 것을 알아두면 좋다.

각 레이어에 대한 정리, LiveData의 문제점 및 해결 책

Presentation Layer

  • 화면과 입력에 대한 처리 등 UI 와 관련된 부분을 담당합니다. Activity, Fragment, View, ViewModel 등을 포함합니다.여기에서 Activity 와 Fragment 는 View 의 역할을 하는, 다시 말해 데이터를 소유하는 것이 아니라 데이터를 표시하기만 하는 역할을 하므로 LiveData 인스턴스를 보유해서는 안 됩니다.
  • LiveData 객체는 주로 AAC ViewModel 에서 관리하게 되는데, 이 또한 Presentation Layer 에 속해 있으니 크게 문제가 될 것은 없어 보입니다.그리고 Presentation Layer 는 Domain Layer 에 대한 의존성을 가지고 있습니다.

 

Data Layer

  • Domain Layer 에 대한 의존성을 갖습니다. Domain 계층의 Repository 인터페이스를 포함하고 있으며, 이에 대한 구현을 Data Layer 에서 하게 됩니다. 그리고 데이터베이스(Local)와 서버(Remote)와의 통신도 Data 계층에서 이루어집니다. 또한 필요하다면 Mapper 클래스를 두어 Data Layer 의 모델을 Domain 계층의 모델로 변환해주는 역할도 이 계층에서 하게 됩니다.
  • Data Layer 클래스에서 LiveData 객체를 작업하고 싶을 수 있지만 LiveData 는 비동기 데이터 스트림을 처리하도록 설계되지 않았습니다. LiveData transfromation 과 MediatorLiveData 등을 통해 이를 처리하게 할 수는 있겠지만, 모든 LiveData 의 관찰은 오직 Main Thread 에서만 진행되기 때문에 한계점을 갖고 있습니다.
  • 안드로이드의 교과서인 디벨로퍼 사이트에서도 이에 대해 명시하고 있으며, Repository 에서는 LiveData 를 사용하지 않도록 권장하면서 동시에 Kotlin Flow 를 사용하도록 권장하고 있습니다. (참고)

Domain Layer

  • 어플리케이션의 비즈니스 로직에서 필요한 UseCase 와 Model 을 포함하고 있습니다. UseCase 는 각 개별 기능 또는 비즈니스 논리 단위이며, Presentation Layer, Data Layer 계층에 대한 의존성을 가지지 않고 독립적으로 분리되어 있습니다.
  • Domain 계층은 안드로이드에 의존성을 가지지 않은 순수 Java 및 Kotlin 코드로만 구성합니다. 여기에는 Repository 인터페이스도 포함되어 있습니다.

LiveData의 문제점

  1. LiveData 는 UI 에 밀접하게 연관되어 있기 때문에 Data Layer 에서 비동기 방식으로 데이터를 처리하기에 자연스러운 방법이 없다.
  2. LiveData 는 안드로이드 플랫폼에 속해 있기 때문에 순수 Java / Kotlin 을 사용해야 하는 Domain Layer 에서 사용하기에 적합하지 않다.
  • 코틀린 코루틴이 발전하면서, Flow 가 등장하게 되었고 많은 안드로이드 커뮤니티에서는 이 Flow 를 이용해서 LiveData 를 대체할 수 있을지에 대한 기대가 생겼지만 쉽지않았다.

💡 이유로는 다음과 같다

  1. Flow 는 스스로 안드로이드 생명주기에 대해 알지 못합니다. 그래서 라이프사이클에 따른 중지나 재개가 어렵습니다.
  2. Flow 는 상태가 없어 값이 할당된 것인지, 현재 값은 무엇인지 알기가 어렵습니다.
  3. Flow 는 Cold Stream 방식으로, 연속해서 계속 들어오는 데이터를 처리할 수 없으며 collect 되었을 때만 생성되고 값을 반환합니다. 만약, 하나의 flow builder 에 대해 다수의 collector 가 있다면 collector 하나마다 하나씩 데이터를 호출하기 때문에 업스트림 로직이 비싼 비용을 요구하는 DB 접근이나 서버 통신 등이라면 여러 번 리소스 요청을 하게 될 수 있습니다.

💡 이를 위해 kotlin 1.41 버전에 Stable API 로 등장한 것이 바로 StateFlow 와 SharedFlow 라고 한다. 일단은 StateFlow만 설명하겠다.

StateFlow

  • StateFlow 는 현재 상태와 새로운 상태 업데이트를 collector 에 내보내는 Observable 한 State holder flow 입니다. 그리고 LiveData 와 마찬가지로 value 프로퍼티를 통해서 현재 상태 값을 읽을 수 있습니다.

💡 StateFlow 는 SharedFlow 의 한 종류이며, LiveData 에 가장 가깝습니다.

 

특징으로는 다음과 같습니다.

  • StateFlow 는 항상 값을 가지고 있고, 오직 한 가지 값을 가집니다.
  • StateFlow 는 여러 개의 collector 를 지원합니다. 이는 flow 가 공유된다는 의미이며 앞서 설명했던 flow 의 단점(3)과는 다르게 업스트림이 collector 마다 중복으로 처리되지 않습니다.
  • StateFlow 는 collector 수에 관계없이 항상 구독하고 있는 것의 최신 값을 받습니다.
  • StateFlow 는 flow builder 를 사용하여 빌드된 flow 가 cold stream 이었던 것과 달리, hot stream 입니다. 따라서 collector 에서 수집하더라도 생산자 코드가 트리거 되지 않고, 일반 flow 는 마지막 값의 개념이 없었던 것과 달리 StateFlow 는 마지막 값의 개념이 있으며 생성하자마자 활성화됩니다.

StateFlow 와 LiveData 는 둘 다 관찰 가능한 데이터 홀더 클래스이며, 앱 아키텍쳐에서 사용할 때 비슷한 패턴을 따릅니다. 즉, MVVM 에서 LiveData 사용되는 자리에 StateFlow 로 대체할 수 있습니다. 그리고 Android Studio Arctic fox 버전부터는 AAC Databinding 에도 StateFlow 가 호환됩니다.

그러나 StateFlow 와 LiveData 는 다음과 같이 다르게 작동합니다.

  • StateFlow 의 경우 초기 상태를 생성자에 전달해야 하지만, LiveData 의 경우는 전달하지 않아도 됩니다.
  • View 가 STOPPED 상태가 되면 LiveData.observe() 는 Observer 를 자동으로 등록 취소하는 반면, StateFlow 는 자동으로 collect 를 중지하지 않습니다. 만약 동일한 동작을 실행하려면 Lifecycle.repeatOnLifecycle 블록에서 흐름을 수집해야 합니다.

예제를 통해 간단한 사용방법도 알아보도록 하겠습니다.예제는 ViewModel 생성 단계에서 Loading 상태로 설정을 해주고, 이어서 비동기 처리를 통해 어떤 결과 값을 받아오면 업데이트해주는 코드를 작성해보겠습니다.

  • 먼저 LiveData 를 사용한 예제입니다.
class MyViewModel(...) : ViewModel() {
    val result: LiveData<Result<UiState>> = liveData {
        emit(Result.Loading)
        emit(repository.fetchItem())
    }
}
  • 이번에는 동일한 처리를 StateFlow 를 사용해보도록 하겠습니다.
class MyViewModel(...) : ViewModel() {
    val result: StateFlow<Result<UiState>> = flow {
        emit(repository.fetchItem())
    }.stateIn(
        scope = viewModelScope, 
        started = WhileSubscribed(5000), // Or Lazily because it's a one-shot
        initialValue = Result.Loading
    )
}

처음에 살펴봤던 예제에 비해 이번 예제에서는 LiveData 와 코드가 사뭇 다릅니다. 눈여겨볼 것은 flow builder 에  stateIn() 함수를 사용하여 Flow 를 StateFlow 객체로 변환해준 것입니다. 그리고 stateIn() 함수 내부에는 파라미터로 scope, started, initialValue 가 있는데 각각 요구하는 값은 아래와 같습니다.

  • scope : 공유가 시작되는 Coroutine Scope.
  • started : 공유가 시작 및 중지되는 시기를 제어하는 전략을 설정하는 파라미터.
    • Lazily : 첫 번째 subscriber 가 나타나면 시작하고, scope 가 취소되면 중지.
    • Eagerly : 즉시 시작되며, scope 가 취소되면 중지.
    • WhileSubscribed : collector 가 없을 때 upstream flow 를 취소. 앱이 백그라운드로 전환되면 취소하게 하는 등의 전략 가능.
  • initialValue : StateFlow 의 초기 값.

정리

다소 긴 내용이었지만, 우리는 LiveData 가 클린 아키텍처 관점에서 갖고 있는 한계점과 이를 대체하기 위한 StateFlow 의 개념과 특징을 비교하며 살펴보았습니다.

마지막으로, LiveData 를 StateFlow 로 사용했을 때 얻게 되는 이점을 정리해보도록 하겠습니다.

  1. 안드로이드 플랫폼에 종속적이었던 LiveData 와는 달리, StateFlow 는 순수 kotlin 라이브러리이기 때문에 Domain Layer 에서 사용할 수 있습니다.
  2. 코루틴을 통해 Work Thread 에서도 비용이 많이 드는 데이터 스트림을 처리할 수 있기 때문에 Data Layer 에서 LiveData 를 사용하는 것보다 향상된 성능으로 사용 가능합니다.
  3. StateFlow 는 zip, flatMapMerge 등 다양한 Flow API 를 사용할 수 있기 때문에 LiveData 보다 풍부하게 활용할 수 있습니다.

💡 StateFlow 와 SharedFlow 가 등장하고, AAC DataBinding 에 StateFlow 가 지원 가능하게 되면서 장기적으로 LiveData 는 deprecated 되는 것이 아니냐는 소문이 돌고 있는데요. 위와 같은 장점들을 고려해봤을 때 앞으로 계속해서 StateFlow 를 위한 API 가 개발이 되고 관련 라이브러리가 많이 등장하게 된다면 충분히 가능성 있는 일이라는 생각이 듭니다.

 

###출처를 기록 못했습니다.. 멋진 분들의 글들을 참고해서 만들었습니다.