Android DataBinding에서 활용하는 LiveData, Observable, StateFlow를 알아본다.



개인 광고 영역

이전에 작성한 글에서 LiveData의 내부 동작을 통해 중복 데이터 사용에 대해서 살펴보았다.

이번 글에서는 LiveData에 대해서 생각하는 부분을 나열하고, DataBinding 시 사용할 수 있는 또 다른 Observable, StateFlow를 간단하게 알아본다.

RxJava는 직접적이지는 않지만 간접적으로 활용해야 한다.

DataBinding을 활용하거나, observe를 할 때 어떤 도구를 활용하는 게 좋을지 가이드를 해보려고 한다.

이 글에서 도움 될 문서는 아래와 같습니다.


이 글에서 알아볼 내용

  • LiveData의 장? 단점?
  • Observable


LiveData는 상태를 저장하고 Lifecycle을 따른다

LiveData는 구독 시 Android Lifecycle을 따르도록 구현되어 있다.

  • onResume/onStart에서 데이터를 받을 준비를 한다.
  • onDestroy에서 더 이상 데이터를 받지 않도록 해지한다.
  • LiveData는 상태를 보관한다.
  • 상태를 보관하고 있기 때문에 onResume/onStart 상태에서 마지막 상태를 다시 흘려보낸다.

마지막 세 번째가 중요한데, 이 부분 덕에 매우 흔한 오류를 경험할 수 있다.

  • AActivity에서 버튼 onClickEvent를 받아, LiveData를 이용 BActivity로 화면 전환을 시도한다.
  • BActivity에서 작업을 마친 후 다시 AActivity로 Back 한다.
  • AActivity의 LiveData는 마지막 이벤트인 버튼의 onClickEvent 정보를 보관하고 이를 onResume/onStart 상태에 흘려보내기 때문에 다시 BActivity로 자동 이동시킨다.

자동으로 무한 반복 시키는 것이다. 초기 LiveData를 다룰 때는 한 번쯤 경험했을 수 있다.

그래서 돌아다니는 솔루션으로 SingleLiveEvent라는 처리를 사용하도록 한다.

이는 사실 솔루션이라기보단 트릭에 가깝다.

LiveData 자체는 상태 값을 보관하도록 만들어져있다. 상태를 보관하고, 이를 Android Lifecycle에 따라 데이터를 받을 수 있도록 만들어져있다.

Lifecycle에 따라 동작하도록 만들어진 LiveData의 observe의 처리를 강제로 null의 상태를 추가해 데이터를 흐르지 않도록 만들거나, 별도의 Boolean 값을 두고 흐르지 않도록 하는 것이다.

이러한 트릭을 이해하고 쓴다면 문제는 없는데, LiveData는 상태를 보관하고, 구독 시 Android Lifecycle을 따르도록 만들어져있다는 것만 이해하면 되겠다.


LiveData는 여러 지점에서 구독할 수 있다.

LiveData는 한 개 이상의 구독이 가능하다. 일반적인 RxJava/Flow에 익숙하다면 쉽게 이해할 수 있다.

LiveData를 아래와 같이 사용하는 것 역시 가능하다는 말이다.

viewModel.liveData.observe(viewLifecycleOwner) {
    Log.e("TEMP", "observe one $it")
}

viewModel.liveData.observe(viewLifecycleOwner) {
    Log.e("TEMP", "observe two $it")
}

viewModel.liveData.observe(viewLifecycleOwner) {
    Log.e("TEMP", "observe three $it")
}

AAC-ViewModel을 Activity에 등록하고, Activity의 어떤 버튼 이벤트를 이용해 화면 전체를 변경해야 할 때 유용할 수 있다.

Fragment에서 구독해두고, 이를 반영하면 되는 것이다.

초기에 개발할 때에 이런 사항을 알고 개발을 하고, 적용했지만 추후에 수정하는 사람이 이를 모르고 수정할 수 있는 단점이 생길 수도 있다.

그럼 의도치 않은 동작을 할 수도 있어질 수 있으므로 주의가 필요하다.


LiveData는 항상 UI에서 동작한다.

LiveData는 항상 UI를 보증한다. 어떤 값을 set/postValue()로 값을 처리하는데, 이전에 작성한 글에서도 언급했지만 항상 MainThread(Android는 UI 스케줄러)에서 동작을 보증한다.

만약 LiveData로 어떠한 값을 가공해야 한다면 맞지 않다. 단순히 상태 값을 저장하더라도 항상 MainThread 임을 기억해야 한다.

이는 문제일 수 있는데, Clean Architecture로 작성한다면 LiveData를 쓰기보단 RxJava나 Flow로 데이터를 처리하고, AAC-ViewModel에서 LiveData로 값을 다시 전달하는 게 좋다.

내부에서 오는 데이터부터 MainThread로 처리한다면 매우 비효율적일 수 있다.


Observable

안드로이드에서는 Observable fields를 다양하게 제공한다. DataBinding에서는 Observable 역시 활용이 가능하다. 사용방법은 문서를 참고하면 자세하게 나와있다.

LiveData처럼 Lifecycle의 상태에 따라 어떠한 값을 처리해야 하는 게 아니라면 Observable을 이용하는 게 더 좋다.

class User : BaseObservable() {

    @get:Bindable
    var firstName: String = ""
        set(value) {
            field = value
            notifyPropertyChanged(BR.firstName)
        }

    @get:Bindable
    var lastName: String = ""
        set(value) {
            field = value
            notifyPropertyChanged(BR.lastName)
        }
}

예를 들면 AActivity에서 BActivity로 이동해서 어떠한 값을 처리하고, 이를 다시 AActivity에서 자동으로 처리하고 싶다면 Lifecycle 상태에 따라 값을 갱신하는 편이 더 좋을 수 있다.

이 경우라면 Lifecycle 따른 값의 처리가 필요한데 LiveData/StateFlow를 활용하는 게 좋다.


StateFlow

Lifecycle을 필요로 하고, 항상 UI로 처리하는 LiveData 대신 StateFlow를 활용할 수 있다. 아래와 같이 MutableStateFlow를 초기화하고, value에 값을 적용한다.

value는 LiveData의 setValue와 동일하게 동작하는데, 마지막 값 만을 보관한다.

class ScheduleViewModel : ViewModel() {

    private val _username = MutableStateFlow<String>("")
    val username: StateFlow<String> = _username

    init {
        viewModelScope.launch {
            _username.value = Repository.loadUserName()
        }
    }
}

xml 사용방법 역시 LiveData나 Observable과 동일한 방법을 활용할 수 있다.

<TextView
    android:id="@+id/name"
    android:text="@{viewmodel.username}" />

이 코드에는 실제 데이터가 흐르게 만들어주고 구독하게 하는 부분의 데이터는 눈에 보이지 않는다. DataBinding에서 알아서 처리해 주기 때문이다.


StateFlow로 LiveData 대체하기

대체하는 방법은 별것 없다. AAC-ViewModel 활용 시 viewModelScope을 활용하면 onDestroy 시점에 cancel()을 처리해 주기 때문에 Lifecycle을 따르게 되고, 그 외 DataBinding의 DataBinding의 lifecycleOwner를 설정하기 때문에 Android Lifecycle에 따라 동작한다.

class ViewModelActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        // Inflate view and obtain an instance of the binding class.
        val binding: UserBinding = DataBindingUtil.setContentView(this, R.layout.user)

        // Specify the current activity as the lifecycle owner.
        binding.lifecycleOwner = this
    }
}

하지만 단순히 LiveData 대신 StateFlow를 활용하기에는 아쉬운 점이 많다. RxJava처럼 추가로 제공하는 Operators들이 더 많은데, 단순 Lifecycle과 UI 스케줄러에서 동작하도록 보증해 주는 LiveData 대신 쓰기엔 덩치가 큰 라이브러리에 해당한다.

좀 더 잘 활용하기 위해서는 StateFlow를 좀 더 학습하고 활용하는 게 당연하게도 좋다.

StateFlow 역시 구독점이 많다.

LiveData의 구독가능 한데, StateFlow 역시 동일하다.

다만 LiveData는 Lifecycle의 onResume/onStart 상태에서 구독을 처리하지만 StateFlow는 그렇지 않다.

이는 Lifecycle에 따라 동작하도록 추가 구현이 필요한 부분이니 참고하고 사용할 필요가 있다.

정리하면 LiveData의 SingleLiveEvent와 같은 처리는 필요치 않고, 반대로 필요할 때 구독할 수 있도록 만들어줄 수 있다.


RxJava

RxJava를 활용할 수도 있다. 하지만 Android의 DataBinding에서 이를 편하게 제공하는 부분은 없다. 눈에 안 보이지만 데이터를 구독하고 처리하는 부분이 자동으로 만들어지기 때문이다.

RxJava는 조금 지저분하게 Activity에서 구독하고, 데이터를 View에 바인딩 하는 방법이 최적으로 보인다. 거기에 Android Lifecycle도 직접 관리하는 게 필요하다.

최소한 직접 관리하는 것에 있어 종료 처리는 필요하기 때문이다.


마무리

LiveData 도 적당하게 사용하면 좋다. 하지만 잘 알고 이해하고 사용하는 게 필요하다. DataBinding을 활용할 때 사용할 수 있는 도구도 세 가지가 있다.

결국 잘 알고 쓰면 좋을 것 같아서 작성한 글이다. 별건 없지만 잘 활용하시길 바란다.



About Taehwan

My name is Taehwan Kwon. I have developed Android for 6 years and blog has been active for eight years.

Comments