DataBinding 을 이용 하면 View 와 데이터를 연동 할 수 있는 옵저버를 이용 하여 실시간으로 뷰에 데이터를 연계 하여 업데이트 할 수 있다. View
에 DataBinding 을 이용 하여 뷰에서 사용자로 인하여 발생하는 어떠한 특정 이벤트를 수신 하여 Rx를 이용하여 처리 할 수 있다.
예를 들면 클릭 이벤트의 처리가 있을 것 이다. 사용자가 의도하던 의도하지 않았던 짧은 시간안에 여러번의 과도한 터치 이벤트를 통제 하여 최초 1회의 터치 이벤트만 수신 받거나 n초 동안에 발생한 이벤트 중 단 하나(처음 혹은 마지막으로 실행되었던)만 수신하여 추후 작업을 안정적으로 실행 하게 하는 방법이 있을 것 이다.
몰론 터치 이벤트 말고도 많은 이벤트가 있다. 이번 예제를 통해서 그러한 main thread 에서 발생할 수 있는 리스크를 가진 이벤트를 제어 하고 제어 하는 방법을 확인 해 볼 수 있을 것 이다.
DataBinding 을 활용 하여 뷰와 데이터를 연계 하기 위해서는 그 중간에서 어뎁터 역할을 해주는 것 이 필요 하다. DataBinding 에서는 @BindingAdapter
라는 어노테이션을 활용 하여 그 역할을 할 수 있다.
아래의 예제 소스는 그 중 하나의 예 이다.
@BindingAdapter({ "viewModel", "onClickHandler" })
public static void setOnClickEventHandler(@NonNull View view, final RxViewModel vm, Action0 listener) {
vm.addSubscriber(
RxViewEventUtil.click(view)
.subscribe(
aVoid -> {
listener.call();
}
)
);
}
@BindingAdapter
라는 어노테이션을 통해서 이 메소드가 데이터 바인등의 xml 에서 사용 할 수 있게 어뎁터 역할을 하는 메소드임을 컴파일러에게 알리고 있다.
어노테이션과 같이 선언된 두개의 패러미터인 viewmodel
, onClickHandler
는 각각 xml 에서 추가 되어 사용 되어 xml으로부터 전달 받게 될 패러미터 들 이다.
xml 에서 viewModel
으로 선언된 RxViewModel
은 내부에서 CompositeSubscription
을 소유한 ViewModel
인터페이스를 구현한 클래스 이다. Rx 의 Subscription
을 패러미터로 받아서 Activity
나 Fragment
의 라이프 사이클 중 onDestroy()
메소드와 같은 메소드 군 에서 조합된 서브스크라이브 들을 일괄적으로 unsubscribe()
한다.
onClickHandler
로 선언된 패러미터인 Action0
인터페이스 는 뷰에서 Rx를 통해서 생성된 옵저버를 통해서 어떤 이벤트를 따로 수신 하게 될 리스너 인터페이스 객체 라고 할 수 있다. 이 객체는 xml을 통해서 정의된 어떤 인스턴스에 존재 하는 Action0
구현체에 접근하여 이벤트를 수신하게 될 경우 이 구현체 인스턴스에 알리게 되는 콜백 이라고 할 수 있겠다. 여기에서 Action0
는 Rx 에서 사용되는 콜백 인터페이스중 하나 이다. 이 리스너용 인터페이스를 직접 정의하여 다르게 사용해도 상관 없다.
RxViewEventUtil
클래스에서 사용되는 정적 메소드인 click()
는 다음과 같다.
public static Observable<Void> click(final @NonNull View view) {
return click(view, DEFAULT_THROTTLE_SEC);
}
public static Observable<Void> click(final @NonNull View view, @IntRange(from = DEFAULT_THROTTLE_SEC) int throttleMs) {
return Observable.create(
new Observable.OnSubscribe<Void>() {
@Override
public void call(Subscriber<? super Void> subscriber) {
view.setOnClickListener(
view1 -> subscriber.onNext(null)
);
}
}
).throttleFirst(throttleMs, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
.observeOn(AndroidSchedulers.mainThread());
}
click()
메소드는 오버로딩을 통해서 사용되는 2개의 메소드가 존재 한다. 내부에서는 Void
제네릭이 선언된 Rx의 Observable
을 반환받는 메소드 들 이다.
생성되는 Observable
은 패러미터로 전달 받는 View
에 OnClick
이벤트를 통지 받는 OnClickListener
를 set 하고 해당 이벤트가 발생 할 때 마다 Observable
을 생성하고 한다.
여기에서 문제가 발생할 수 있는데, 어떤 사용자는 버튼같은 어떠한 뷰를 연속으로 많이 터치 할 수 있다. 문제는 이 때마다 어떠한 서버와 통신 콜이나 작업을 많이 하게 된 다면 쓸데 없는 작업을 반복해서 하게 된다. 이런 경우 단 한번의 작업만 하면 되는데 말이다. 이런 경우에는 사용자의 입력을 제한 할 필요가 있다.
보통 그래서 입력 후 View
의 입력 제한을 걸거나 사용을 못하게 하기도 한다. 프로그레스바 를 이용한 터치 불가능 하게 만드는 ux 가 그 하나의 예 일 것 이다. 하지만 여기에서 사용하고자 하는 바는 Rx 를 이용하여 생성되는 이벤트를 n초간 발생한 이벤트 중 하나만 서브스크라이브 하 도록 안전한 실행 환경을 제공하고자 하는 것 이다.
그래서 Rx의 throttleFirst()
오퍼레이터를 이용 하여 옵저버의 생성을 제어 한다. 이 오퍼레이터에서는 주어지는 시간 동안에 생성되는 옵저버 중 처음으로 생성된 것만 서브스크라이브 하고 나머지는 그 남은 시간동에는 무시 한다. 문제가 될 수 있는 환경에서 사용할 수 있는 좋은 오퍼레이터라고 할 수 있겠다.
<Button android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#fff"
android:background="@drawable/button_default"
android:text="로그인"
android:enabled="@{vm.isEnableValidationIDButton}"
bind:rxViewModel="@{vm}"
bind:onClickHandler="@{vm.onClickValidationIdButton}"/>
위 버튼의 한 xml 레이아웃 설정의 예 이다. ViewModel
으로 사용 되는 vm
은 이미 선언 되었다 가정하고 만들어졌다. 위 @BindingAdapter
에서 선언된 패러미터들을 각자 패러미터로 작성 한 것 을 확인 할 수 있다. 만약 2개의 패러미터 중 하나라도 추가 하지 않으면 오류가 발생 할 것이다. 그런점을 신경 쓰면서 사용 하면 된다.
데이터바인딩을 사용 하게 된다면 이러한 유틸리티 메소드들을 생각보다 많이 만들게 된다. 특히 단위 테스팅을 위해서 Context
나 View
의 접근을 최소로 하는 비즈니스로직을 수행하는 ViewModel
이나 Presenter
등의 클래스를 만들거나 특정 의존성에 의해 주입받는 DI 등을 구현 하게 된 다면 뷰에 대한 처리나 이벤트 처리를 테스팅과 관련없이 분리 해서 개발 해야 한다. UI 테스트가 아닌 이상 주입하기 어려운 객체는 빼야 하니 말이다. 그런 점 에서 이러한 뷰에 대한 데이터바인딩에 처리는 상당히 귀찮은게 사실이기도 하다.
하지만 이러한 유틸리티 메소드들은 만들어 두면 자주사용 하게 된다. 아직 문제점이(액티비티나 프래그먼트의 라이프 사이클에 의해 뷰모델 같은 것 들이 뷰 보다 먼저 파괴 되어 널포인트 예외가 발생할 수도 있다) 존재하지만 매력넘치는 기술인 것은 사실 이다.