Tango 公司的开发团队,把Android Data Binding 和RxJava 结合到一起。 下面来看看他们是如何使用的。
比如下面是一个按钮中使用的 binding 表达式:
<Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:enabled="@{StringUtils.isNotNullOrEmpty(viewModel.firstName) && StringUtils.isNotNullOrEmpty(viewModel.lastName)}" android:onClick="@{() -> viewModel.buttonClicked()}" android:text="@string/btn_hello"/>
上面使用一个表达式来确定 enabled 的状态。但是上面的表达式虽然可以正常工作,但是还是有些缺陷的:
- 无法针对这个表达式编写单元测试
- 在其他的 XML 布局文件中无法重用这个表达式
- 表达式太复杂了,不太容易理解
因此可以把上面的表达式优化为下面这样:
<Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:enabled="@{viewModel.helloButtonEnabled}" android:onClick="@{() -> viewModel.buttonClicked()}" android:text="@string/btn_hello_rx"/>
在 ViewModel 中创建一个类型为 ObservableField 的 helloButtonEnabled 变量,该变量绑定到 View 的enabled 属性。下面是 MainViewModel 类的代码:
public static class MainViewModel { public ObservableField<String> firstName = new ObservableField<>(); public ObservableField<String> lastName = new ObservableField<>(); public ObservableField<String> helloText = new ObservableField<>(); public ObservableBooleanhelloButtonEnabled = new ObservableBoolean(false); public MainViewModel() { Observable.combineLatest(toObservable(firstName), toObservable(lastName), (firstName, lastName) -> StringUtils.isNotNullOrEmpty(firstName) && StringUtils.isNotNullOrEmpty(lastName)) .subscribe(result -> { helloButtonEnabled.set(result); if (!result) { helloText.set(StringUtils.EMPTY); } }, Throwable::printStackTrace); } public void buttonClicked() { helloText.set(String.format("Hello %s %s !", firstName.get(), lastName.get())); } }
注意上面的代码在构造函数中, 使用 combineLatest 把 firstName 和 lastName 组合起来来判断 helloButtonEnabled 的值。如果 firstName 或者 lastName 为 空 则设置按钮不可以点击,并且清空 helloText 的文本。
单元测试也很方便编写:
public class MainViewModelTests { @Test public void mainViewModel_firstName_lastName_helloButtonEnabledSetToTrue() throws Exception { MainViewModelmainViewModel = new MainViewModel(); assertFalse(mainViewModel.helloButtonEnabled.get()); mainViewModel.firstName.set("a"); mainViewModel.lastName.set("b"); assertTrue(mainViewModel.helloButtonEnabled.get()); } @Test public void mainViewModel_firstName_lastNameEmpty_helloButtonEnabledSetToFalse() throws Exception { MainViewModelmainViewModel = new MainViewModel(); mainViewModel.firstName.set("a"); mainViewModel.lastName.set(""); assertFalse(mainViewModel.helloButtonEnabled.get()); } }
上面这种方式的好处:
- 业务逻辑封装到 ViewModel 中
- 业务逻辑可以测试
- XML 布局文件可读性更好
- 没有重复的代码
下图为示例应用的效果,代码位于 github: https://github.com/TangoAgency/android-data-binding-rxjava
如何做到的呢?
上面的代码核心点在于 toObservable 这个函数的实现。该函数使用 ObservableField 的 addOnPropertyChangedCallback 函数把异步回调函数封装为 RxJava 的 Observable。代码如下:
import android.databinding.ObservableField;import android.support.annotation.NonNull;import rx.AsyncEmitter;import rx.Observable;import static android.databinding.Observable.OnPropertyChangedCallback; public class RxUtils { private RxUtils() { } public static <T> Observable<T> toObservable(@NonNull final ObservableField<T> observableField) { return Observable.fromEmitter(asyncEmitter -> { final OnPropertyChangedCallbackcallback = new OnPropertyChangedCallback() { @Override public void onPropertyChanged(android.databinding.ObservabledataBindingObservable, int propertyId) { if (dataBindingObservable == observableField) { asyncEmitter.onNext(observableField.get()); } } }; observableField.addOnPropertyChangedCallback(callback); asyncEmitter.setCancellation(() -> observableField.removeOnPropertyChangedCallback(callback)); }, AsyncEmitter.BackpressureMode.LATEST); } }
参考 把异步回调操作转换到 RxJava 中 来了解如何封装 Observable。