Shunz Android Dev Note

안드로이드 LiveData와 Flow는 각각 언제 사용해야 할까? 본문

Android

안드로이드 LiveData와 Flow는 각각 언제 사용해야 할까?

_Shun_ 2023. 11. 29. 11:32

 

독자는 Android 앱을 개발하면서 LiveData를 자주 사용 해 보았을것입니다. LiveData는 사용하기 쉽고, UI에 표시하기 위하여 필요한 상태를 옵저빙함에 있어 안전하게 라이프 사이클을 관리 할 수 있도록 도와주는 편리한 툴입니다. 생성하기도 쉽고 업데이트하기도 쉽습니다. 또한 옵저빙하기도 쉽습니다. 그래서 개발자들이 간단한 옵저빙이 필요한 모든 곳에서 사용하는 것일 수 있다고 생각합니다. Presentation(UI) 계층에는 괜찮거나, 클린코드 관점에서의 죄책감도 들지 않고, 해가 되지는 않습니다. 하지만 Domain 이나 Data 계층에서는 더 나은 솔루션이 있습니다. 사실, 이런 주제가 나온 것은 처음이 아닙니다. 블로그 게시글, 샘플 및 책에서 좋은 솔루션으로 제공되는 repository 파일에서 LiveData를 가끔씩 보았습니다. 필자는 개발자들이 LiveData는 만능이고 장점만 존재한다고 생각하는 경우가 있다고 생각합니다.

 

repository에서 LiveData를 사용하면 사실 함정이 있습니다. 필자는 이를 안티패턴이라고 봅니다.

 

 

LiveData가 왜 만능이 아닐까?

LiveData는 안드로이드의 라이프사이클을 인식합니다. repository에 이것이 필요 할까요? repository는 일반적으로 의존성 주입 방식으로 제공되며 singleton 객체입니다. 즉, 안드로이드 라이프사이클에 대하여 알 필요가 없습니다.

 

LiveData는 항상 메인 쓰레드에서 실행되고, 우리는 이것을 변경할 수 없습니다. 개발자들은 repository가 이렇게 동작되는것을 원하지 않을 것입니다. repository는 종종 네트워크 또는 디스크 IO 작업을 포함할 수 있는 다른 데이터 소스에서 작동합니다. 독자가 작성중인 repository에서 이러한 코드를 작성한다면 적절한 dispatcher에서 이러한 종류의 작업을 실행할 수 있어야 한다고 생각합니다. 이 작업을 잘못 수행하면 UI가 버벅일수 있기 때문에 필자가 생각하기에 이 부분이 결정적인 문제라고 생각합니다.

 

LiveData는 MediatorLiveData 와 결합하거나 변환하는 기능을 가지고 있지만, Flow는 다양한 우아한 연산자를 선택할 수 있습니다. 이것이 더 편리할 경우의 것이라는 확실한 주장은 아닙니다.

 

마지막으로, LiveData를 Flow로 전환해야 하는 가장 큰 이유 중 하나는, Flow가 코틀린 코루틴 라이브러리의 일부라는 것입니다. 이는 코틀린 코루틴이 실행되는 모든 플랫폼에서 실행된다는 것을 의미합니다. 멀티플랫폼 프로젝트에서 이를 사용할 수 있습니다. 달리 표현하면, LiveData 대신 repository에서 Flow를 사용하면 다른 플랫폼에서 repository를 사용할 때 제거하고 교체해야 하는 안드로이드 라이브러리가 하나 줄어듭니다.

 

 

어떻게 LiveData를 Flow로 변환할까?

코드상에서 LiveData를 StateFlow로 변환하고, MutableLiveDataMutableStateFlow로 변환할 수 있습니다.

private val _profile: MutableLiveData<UserProfile?> by lazy { MutableLiveData() }
val profile: LiveData<UserProfile?> = _profile

 

변환된 코드

private val _profile: MutableStateFlow<UserProfile?> by lazy { MutableStateFlow(null) }
val profile: StateFlow<UserProfile?> = _profile.asStateFlow()

 

 

StateFlow에 값 할당하기

StateFlow는 항상 초기값을 필요로 합니다. 그래서 독자는 StateFlow를 선언할때 항상 전달해야 합니다. 그러면, 어떻게 값을 할당할 수 있는지 코드를 보겠습니다.

_profile.value = UserProfile(name = "Shun", email = "abcd@gmail.com")

 

MutableLiveData에 값을 할당하는것과 크게 다르지 않고, 매우 쉽습니다.

 

 

Flows를 수집하기

LiveData와 떨어져서 값을 얻기 위해서 우리는 StateFlow의 터미널 연산자를 통하여 값을 수집하여야 합니다. 데이터를 옵저빙하는 아키텍처 계층에 따라 약간 다르게 보일 수 있습니다.

 

ViewModel 에서 수집하기

repository에서 스트림을 수집하기만 하면 다음과 같이 수행할 수 있습니다.

fun fetchProfile() {
    viewModelScope.launch {
        repository
          .someOrOtherFlow
          .collect { result ->
              handleResult(result)
          }
    }
}

 

flows를 결합하기

만약 독자가 MediatorLiveData를 사용하는 상황이라면, Flows를 변환하고 결합할수 있는 중간 연산자가 많이 있습니다. 두 개의 observables를 어떻게 결합하는지 살펴보겠습니다. LiveData 를 사용하는 경우에는 MediatorLiveData에서 adSource()를 사용 했을 것입니다. flow를 사용한 경우의 예제 코드입니다.

data class UiState(
    val str: String = "",
    val num: Int = 0,
)

fun testCombineOperator() {
    val flow1 = flowOf("a", "b")
    val flow2 = flowOf(1, 2)
    val uiState = flow1.combine(flow2) { value1, value2 ->
        // flow1 또는 flow2의 항목이 emit 되었을때 이 코드 블락이 실행.
        UiState(value1, value2)
    }
        .stateIn( // stateIn을 사용하여 StateFlow로 변환
            scope = viewModelScope,
            started = WhileSubscribed(5000),
            initialValue = UiState()
        )
}

 

 

UI - Compose 사용

편리한 collectAsStateWithLifecycle() 함수를 사용합니다.

// LiveData
private val _profile2: MutableStateFlow<UserProfile?> by lazy { MutableStateFlow(null) }
val profile2: StateFlow<UserProfile?> = _profile2.asStateFlow()

// Compose
viewModel.profile2.collectAsStateWithLifecycle()

 

collectAsState 와 collectAsStateWithLifecycle 비교

 

Activity/Fragment의 라이프사이클에 따라 Flow 수집이 어떻게 동작되는지 확인 할 수 있습니다. collectAsStateWithLifecycle로 Flow 수집을 진행하면 화면이 포그라운드 상태일때만 동작합니다. collectAsState와의 차이점 또는 어떤 경우에 사용하는지에 대하여 알고 싶다면 이글을 참조 해주세요.

 

UI - XML

여기에 좋은 포스팅이 있지만, 코드를 잠깐 살펴보면 아래와 같습니다.

lifecycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.STARTED) {
        viewModel.profile.collect {
            // Profile 데이터를 사용.
        }
    }
}

 

 

결론

Activity, Fragment와 같은 presentation에서는 lifecycle을 알고 있는 LiveData를 사용하는것이 좀 더 장점이 더 많지만, 그 이외의 레이어(domain, data 등)에서는 Flow가 훨씬 많은 장점을 가지고 있습니다. 적절한 도구를 선택해서 사용하는것은 독자의 선택입니다. 한가지 명확히 말씀드리고 싶은점은 LiveData가 만능이 아니라는 점입니다.

 


 

긴 글 읽어주셔서 감사합니다.