無論是簡單的登錄頁面刻坊,還是復(fù)雜的訂單提交頁面枷恕,表單的前端驗證(比如登錄名和密碼都符合基本要求才能點亮登錄按鈕)都是必不可少的步驟。本文展示了如何用RxJava來方便的處理表單提交前的驗證問題谭胚,例子采用了Android上的一個簡單的登錄頁面
內(nèi)容提要
- 傳統(tǒng)的驗證方式
- combineLatest操作符
- 用combineLatest處理表單驗證
- combineLatest和zip的區(qū)別
本文中所演示的例子sample代碼位于RxAndroidDemo徐块,參見loginActivity這個文件
博客園對應(yīng)原文
傳統(tǒng)的驗證方式
這里我們用最簡單的例子來說明,如上圖漏益,一個email輸入和一個password輸入蛹锰,下方是一個登錄的按鈕深胳。只有當(dāng)email輸入框內(nèi)容含有@字符绰疤,password輸入框內(nèi)容大于4個,才點亮下方的按鈕舞终。
首先你用EditText還是繼承自EditText的控件轻庆,一般來說監(jiān)聽它的內(nèi)容,都是用addTextChangedListener敛劝。但是如何顯然登錄按鈕的enable與否是同時要判斷email和password的余爆,兩個都成立才可點亮。所以我們在email的TextWatcher中除了要判斷email是否符合條件以外夸盟,還要同時判斷password是否符合條件蛾方,這樣以來就容易造成多重判斷。
試想如果你在提交一個訂單的表單上陕,上面是十幾個輸入框桩砰,每個輸入的內(nèi)容都同時符合條件才可以點亮“提交”按鈕,這是多么痛苦的事情————每一個輸入框的改變都要同時再判斷其他十幾個輸入框內(nèi)容是否符合(實際上此時其他十幾個輸入框沒變化)
combineLatest操作符
combineLatest是RxJava本身提供的一個常用的操作符释簿,它接受兩個或以上的Observable和一個FuncX閉包亚隅。當(dāng)傳入的Observable中任意的一個發(fā)射數(shù)據(jù)時,combineLatest將每個Observable的最近值(Lastest)聯(lián)合起來(combine)傳給FuncX閉包進行處理庶溶。要點在于
- combineLatest是會存儲每個Observable的最近的值的
-
任意一個Observable發(fā)射新值時都會觸發(fā)操作->“combine all the Observable's lastest value together and send to Function”
combineLatest操作符
用combineLatest處理表單驗證
首先我們寫上email和password的驗證方法煮纵,一個需要含有@字符懂鸵,一個要求字符數(shù)超過4個:
private boolean isEmailValid(String email) {
//TODO: Replace this with your own logic
return email.contains("@");
}
private boolean isPasswordValid(String password) {
//TODO: Replace this with your own logic
return password.length() > 4;
}
隨后,我們針對email和password分別創(chuàng)建Observable行疏,發(fā)射的值即為各自edittext的變化的內(nèi)容匆光,而call回調(diào)方法的返回值是textWatcher中afterTextChanged方法的傳入?yún)?shù):
Observable<String> ObservableEmail = Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(final Subscriber<? super String> subscriber) {
mEmailView.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
subscriber.onNext(s.toString());
}
});
}
});
Observable<String> ObservablePassword = Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(final Subscriber<? super String> subscriber) {
mPasswordView.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
subscriber.onNext(s.toString());
}
});
}
});
最后,用combineLastest將ObservableEmail和ObservablePassword聯(lián)合起來進行驗證:
Observable.combineLatest(ObservableEmail, ObservablePassword, new Func2<String, String, Boolean>() {
@Override
public Boolean call(String email, String password) {
return isEmailValid(email) && isPasswordValid(password);
}
}).subscribe(new Subscriber<Boolean>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(Boolean verify) {
if (verify) {
mEmailSignInButton.setEnabled(true);
} else {
mEmailSignInButton.setEnabled(false);
}
}
});
onNext中的verify就是經(jīng)過combineLastest對兩者驗證后組合的結(jié)果酿联。
參見LoginActivity的bindView()方法
這里殴穴,即使表單非常復(fù)雜,實際上你需要擴展的話就很容易了:
- 對每個EditText封裝一個Observable
- 改寫這句話货葬,加入新的邏輯:
return isEmailValid(email) && isPasswordValid(password);
覺得為每一個EditText封裝一個Observable要寫很多重復(fù)代碼采幌?放心,Jake Wharton大神早已經(jīng)想到震桶,RxBinding中的RxTextView就可以解決這個問題:
Observable<CharSequence> ObservableEmail = RxTextView.textChanges(mEmailView);
Observable<CharSequence> ObservablePassword = RxTextView.textChanges(mPasswordView);
Observable.combineLatest(ObservableEmail, ObservablePassword, new Func2<CharSequence, CharSequence, Boolean>() {
@Override
public Boolean call(CharSequence email, CharSequence password) {
return isEmailValid(email.toString()) && isPasswordValid(password.toString());
}
}).subscribe(new Subscriber<Boolean>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(Boolean verify) {
if (verify) {
mEmailSignInButton.setEnabled(true);
} else {
mEmailSignInButton.setEnabled(false);
}
}
});
參見LoginActivity的bindViewByRxBinding()方法
combineLatest和zip的區(qū)別
zip是和combineLatest有點像的一個操作符休傍,接受的參數(shù)也是兩個或多個Observable和一個閉包。但是區(qū)別在于:
- zip是嚴(yán)格按照順序來組合每個Observable蹲姐,比如ObservableA的第一個數(shù)據(jù)和ObservableB的第一個數(shù)據(jù)組合在一起發(fā)射給FuncX來處理磨取,兩者的第N個數(shù)據(jù)組合在一起發(fā)射給FuncX來處理,以此類推
- zip并不是任意一個Observable發(fā)射數(shù)據(jù)了就觸發(fā)閉包處理柴墩,而是等待每個Observable的第N個數(shù)據(jù)都發(fā)射齊全了才觸發(fā)
zip一般用于整合多方按照順序排列的數(shù)據(jù)忙厌。