Skip to content

Latest commit

 

History

History
86 lines (62 loc) · 7.34 KB

170110.md

File metadata and controls

86 lines (62 loc) · 7.34 KB

DataBinding과 Rx를 이용하여 View 이벤트 핸들링 하기

DataBinding 을 이용 하면 View 와 데이터를 연동 할 수 있는 옵저버를 이용 하여 실시간으로 뷰에 데이터를 연계 하여 업데이트 할 수 있다. View 에 DataBinding 을 이용 하여 뷰에서 사용자로 인하여 발생하는 어떠한 특정 이벤트를 수신 하여 Rx를 이용하여 처리 할 수 있다.

예를 들면 클릭 이벤트의 처리가 있을 것 이다. 사용자가 의도하던 의도하지 않았던 짧은 시간안에 여러번의 과도한 터치 이벤트를 통제 하여 최초 1회의 터치 이벤트만 수신 받거나 n초 동안에 발생한 이벤트 중 단 하나(처음 혹은 마지막으로 실행되었던)만 수신하여 추후 작업을 안정적으로 실행 하게 하는 방법이 있을 것 이다.

몰론 터치 이벤트 말고도 많은 이벤트가 있다. 이번 예제를 통해서 그러한 main thread 에서 발생할 수 있는 리스크를 가진 이벤트를 제어 하고 제어 하는 방법을 확인 해 볼 수 있을 것 이다.

1. 구현

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을 패러미터로 받아서 ActivityFragment의 라이프 사이클 중 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은 패러미터로 전달 받는 ViewOnClick이벤트를 통지 받는 OnClickListener를 set 하고 해당 이벤트가 발생 할 때 마다 Observable을 생성하고 한다.

여기에서 문제가 발생할 수 있는데, 어떤 사용자는 버튼같은 어떠한 뷰를 연속으로 많이 터치 할 수 있다. 문제는 이 때마다 어떠한 서버와 통신 콜이나 작업을 많이 하게 된 다면 쓸데 없는 작업을 반복해서 하게 된다. 이런 경우 단 한번의 작업만 하면 되는데 말이다. 이런 경우에는 사용자의 입력을 제한 할 필요가 있다.

보통 그래서 입력 후 View 의 입력 제한을 걸거나 사용을 못하게 하기도 한다. 프로그레스바 를 이용한 터치 불가능 하게 만드는 ux 가 그 하나의 예 일 것 이다. 하지만 여기에서 사용하고자 하는 바는 Rx 를 이용하여 생성되는 이벤트를 n초간 발생한 이벤트 중 하나만 서브스크라이브 하 도록 안전한 실행 환경을 제공하고자 하는 것 이다.

그래서 Rx의 throttleFirst() 오퍼레이터를 이용 하여 옵저버의 생성을 제어 한다. 이 오퍼레이터에서는 주어지는 시간 동안에 생성되는 옵저버 중 처음으로 생성된 것만 서브스크라이브 하고 나머지는 그 남은 시간동에는 무시 한다. 문제가 될 수 있는 환경에서 사용할 수 있는 좋은 오퍼레이터라고 할 수 있겠다.

2. 사용 예

<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개의 패러미터 중 하나라도 추가 하지 않으면 오류가 발생 할 것이다. 그런점을 신경 쓰면서 사용 하면 된다.

데이터바인딩을 사용 하게 된다면 이러한 유틸리티 메소드들을 생각보다 많이 만들게 된다. 특히 단위 테스팅을 위해서 ContextView의 접근을 최소로 하는 비즈니스로직을 수행하는 ViewModel이나 Presenter등의 클래스를 만들거나 특정 의존성에 의해 주입받는 DI 등을 구현 하게 된 다면 뷰에 대한 처리나 이벤트 처리를 테스팅과 관련없이 분리 해서 개발 해야 한다. UI 테스트가 아닌 이상 주입하기 어려운 객체는 빼야 하니 말이다. 그런 점 에서 이러한 뷰에 대한 데이터바인딩에 처리는 상당히 귀찮은게 사실이기도 하다.

하지만 이러한 유틸리티 메소드들은 만들어 두면 자주사용 하게 된다. 아직 문제점이(액티비티나 프래그먼트의 라이프 사이클에 의해 뷰모델 같은 것 들이 뷰 보다 먼저 파괴 되어 널포인트 예외가 발생할 수도 있다) 존재하지만 매력넘치는 기술인 것은 사실 이다.