RxJava로 데이터 바인딩 흉내내기

이현우·2021년 9월 26일
0

Deep Dive: Android

목록 보기
9/10
post-thumbnail

Motivation

이번 드로이드 나이츠에서 정승욱님이 발표하신 액티비티: 코드 제로를 보면서 RxJava를 깊게 다루고 다양한 기술을 이해할 수 있는 근본을 갖춰보고 싶었습니다.

그래서 기존에 작성해 두었던 Introduction to ViewModel/LiveData를 RxJava로 리팩토링 해봄으로써 RxJava를 어떻게 사용할 수 있는 지를 이 게시글에서 공유해보고자 합니다. 위에 작성했던 ViewModel/LiveData를 모른다면 아래 글에서 확인해 보세요

Introduction to ViewModel/LiveData

뷰모델 입문

Data Binding 입문

LiveData 입문

Cold vs Hot

RxJava를 처음 배운다면 가장 먼저 난관을 겪을 수 있는 부분이 바로 Cold와 Hot 개념일 것입니다. subscribe를 해야 받아온다느니 마느니 개념 자체가 어려울 수 있을 것 같아서 아래와 같은 상황을 예시로 들어보겠습니다.

Cold는 여러분이 주문하는 것과 같습니다. 여러분이 음식점을 들어가서 종업원에게 국밥 한그릇을 외쳐야 국밥이 만들어지고 여러분 앞으로 국밥 한그릇이 전달되겠죠?

Cold Observable은 국밥을 만드는 사람(Producer)국밥을 먹는 사람(Subscriber)가 있어야 만들어집니다.
그래서 Cold Observable 개념을 설명할 때 subscribe을 해야 데이터를 받아올 수 있다는 말이 바로 데이터 생산자와 구독자 모두 있어야 하는 Observable을 줄여서 설명한 것이라고 생각할 수 있겠네요.

그렇다면 Hot은 어떻게 이해할 수 있을까요? Hot은 대학교 1교시를 생각해보시면 됩니다. 이게 무슨소리냐구요?

여러분이 9시까지 강의실에 도착하든, 지하철에 있든, 침대에서 다가올 미래를 무시한 채 잠에 빠지든 수업은 어쨌든 반드시 시작될 것입니다.

missing 아아 수업은 must go하지 않아도 되는데

이렇게 원하는 사람이 없음에도 불구하고수업을 듣는 사람들의 참여여부(Subscriber의 subscribe 여부)와 관계 없이 수업은 진행되는 것(Producer)과 같은 개념이 Hot Observable이라고 생각하시면 되겠습니다. 즉, subscribe가 없어도 데이터 생산자만 있어도 되는 경우를 말하는 것이겠죠.

그렇다면 RxJava에서는 이 개념을 어떻게 구현하고 있을까요? 이 개념과 같은 경우 이 게시글에서 조금 더 집중적으로 다루고 있으므로 참고하시면 좋을 것 같습니다. 요약을 해드리자면 Cold Observable은 Observable, Single과 같은 Observable 클래스들로, Hot Observable은 Cold에서 Hot Observable로 변환해주는 Subject 클래스를 활용하여 만들 수 있습니다.

PublishSubject와 Observable로 데이터 바인딩 구현하기

일전에 보여줬던 예시에서는 위의 입력창에 텍스트를 입력하면 아래 TextView에 버튼을 누르거나 실시간으로 글이 나왔었습니다.

이번에는 입력이 될 때 16자 이상이 되면 15자 이하로 작성하라는 경고문구를 띄워보는 것으로 예제를 살짝 변경해보겠습니다.

데이터 바인딩 구현(이라고 하지만 완벽한 구현은 아님)

우선 EditText에서 글이 바뀔때마다 Cold Observable을 만들어야 하므로 Cold Observable을 Hot Observable로 전환하는 PublishSubject 변수를 ViewModel에 만들고 EditText의 doAfterTextChanged 함수를 이용하여 텍스트가 바뀔때마다 PublishSubject에 넣어주도록 합시다.

// MainViewModel
val nameSubject: PublishSubject<String> = PublishSubject.create()

// MainActivity
binding.etMainName.doAfterTextChanged {
    mainViewModel.nameSubject.onNext(it.toString())
}

+) 왜 완벽한 구현이라고 볼 수 없나요?
완벽한 구현이 아니라고 하는 것은 ViewController 클래스라고도 볼 수 있는 Activity 클래스에 View와 ViewModel 연결 코드가 존재하기 때문에 MVVM 패턴에서 일컫는 데이터 바인딩 개념과 동떨어져 있기 때문입니다.

The Data Binding Library is a support library that allows you to bind UI components in your layouts to data sources in your app using a declarative format rather than programmatically.

이를 해결하려면 InverseBindingAdapter를 활용하여 연결시키면 됩니다.

조건에 맞게 화면에 표시(Subscribe 단계)

이제 PublishSubject로 받아온 데이터를 아래에 있는 TextView에 보여줘야겠죠? 저는 위의 조건을 아무것도 입력하지 않았을 때, 1~15글자를 입력했을 때, 16글자 이상 입력하는 경우가 있다고 생각하여, 각각의 이벤트로 분기시킬 수 있도록 ViewModel 내부에 이벤트에 해당하는 sealed class를 만들었습니다.

    // MainViewModel 내부
    sealed class NameState {
        object Blank : NameState()
        // Name 클래스 내부에 text를 담아서 subscribe를 할 때 이 name을 TextView에 넣어준다
        data class Name(val name: String) : NameState()
        object IsNotMatchWithCondition : NameState()
    }

이제 PublishSubject를 각각에 이벤트에 맞는 Observable로 변환시켜야겠죠? 이는 map 함수를 활용하여 쉽게 변환시킬 수 있습니다.

    val name = nameSubject.observeOn(AndroidSchedulers.mainThread())
        .map {
            when (it.length) {
                0 -> NameState.Blank
                in 1..15 -> NameState.Name(it)
                else -> NameState.IsNotMatchWithCondition
            }
        }

이제 이 name 변수를 MainActivity에서 subscribe 해야겠죠? subscribe를 하면서 다음과 같은 로직을 수행합니다.

  • 각각의 이벤트에 맞게 TextView에 보여줄 text를 설정한다
  • onDestroy가 호출되면 subscribe가 종료될 수 있게 CompositeDisposable에 Disposable을 add한다

이는 다음과 같이 표현될 수 있습니다.

        mainViewModel.name.subscribe {
            binding.txtName.text = when (it) {
                is MainViewModel.NameState.Blank -> ""
                is MainViewModel.NameState.Name -> it.name
                is MainViewModel.NameState.IsNotMatchWithCondition -> "15자 이하로 작성하시오"
            }
        }.apply { compositeDisposable.add(this) }

구현 영상

소결

진행중인 프로젝트라던가 회사일에 치여서 벨로그에 글을 작성할 시간이 없는데...

코루틴 시리즈라던가 테스팅 시리즈와 같은 굵직굵직한 게시글 시리즈가 아직도 종결을 짓지 못해서 참 아쉽네요. 얼른 프로젝트 정리를 하면서 다시 공부 빡세게 하고 시리즈 연재도 하면서 더 많은 지식을 공유하고 싶습니다.

+) 아 제가 운영하는 스터디에서 블로그를 운영중입니다. 주로 안드로이드 내부 코드, Under the hood와 관련된 글들이 많이 올라올 예정입니다. 많은 관심 부탁드립니다.

https://medium.com/write-android

참고 코드

https://github.com/l2hyunwoo/SampleDataBinding/tree/feature/rxdatabinding

profile
이현우의 개발 브이로그

0개의 댓글