-->

Neater

Be A Warrior, Not A Worrier

RxJava 在 Android 开发中的初次尝试(上)

接触 RxJava 有一段时间了,写的都是一些小打小闹的 Sample 程序,不如就在实际项目中一试伸手。

许多 App 都有注册功能,当没有输入或者输入不符合业务逻辑时,会有错误提示。好点的用户体验应该是,只有当所有输入符合业务逻辑时,才会让注册按钮变成可点击状态,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
public class MainActivity extends Activity {

    @InjectView(R.id.user_name)
    EditText userName;
    @InjectView(R.id.password)
    EditText password;
    @InjectView(R.id.confirmedPassword)
    EditText confirmedPassword;
    @InjectView(R.id.submit_button)
    Button submitButton;

    private String userNameString;
    private String passwordString;
    private String confirmedPasswordString;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.inject(this);

        addTextChangedListenners();
    }

    private void addTextChangedListenners() {

        userName.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {

            }

            @Override
            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
                userNameString = charSequence.toString();
                validateUserInput();
            }

            @Override
            public void afterTextChanged(Editable editable) {

            }
        });

        password.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {

            }

            @Override
            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
                passwordString = charSequence.toString();
                validateUserInput();

            }

            @Override
            public void afterTextChanged(Editable editable) {

            }
        });

        confirmedPassword.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {

            }

            @Override
            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
                confirmedPasswordString = charSequence.toString();
                validateUserInput();
            }

            @Override
            public void afterTextChanged(Editable editable) {

            }
        });
    }
    private void validateUserInput() {

        boolean isUserNameValid = userNameString != null && userNameString.trim().length() > 2;
        boolean isPasswordValid = passwordString != null && passwordString.trim().length() > 3;
        boolean isPasswordEqual = confirmedPasswordSting != null && confirmedPasswordSting.trim().equals(passwordString);
        boolean enable = isUserNameValid && isPasswordValid && isPasswordEqual;
        submitButton.setEnabled(enable);
    }

}

用传统方法,实现起来费时费力、容易出错、难以维护,让我们开启响应式模式,用 RxJava 来实现,不过先上个流程图先。

flow

如图所示,在流中需要传递一个 model,为简化流程,用户名只规定长度大于2,密码长度大于3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class InputValidation {
    private String userName;
    private String password;
    private String confirmedPassword;

    public InputValidation(String userName, String password, String confirmedPassword) {
        this.userName = userName;
        this.password = password;
        this.confirmedPassword = confirmedPassword;
    }

    public boolean allowSubmit() {
        boolean isUserNameValid = userName.trim().length() > 2;
        boolean isPasswordValid = password.trim().length() > 3;
        boolean isPasswordEqual = confirmedPassword.trim().equals(password);
        boolean allowSubmit = isUserNameValid && isPasswordValid && isPasswordEqual;
        return allowSubmit;
    }

    public static String stringOfTextChangEvent(OnTextChangeEvent onTextChangeEvent) {
        return onTextChangeEvent.text().toString();
    }
}

创建并转成字符串流,WidgetObservable.text 是 RxAndroid 提供的获取 EditText 输入文本的 API,map中传入的是转换方法的引用,这也是 Java8 才支持的。

1
2
3
4
5
6
7
8
private void initRx() {
    Observable<String> userNameObservable = WidgetObservable.text(userName)
            .map(InputValidation::stringOfTextChangEvent);
    Observable<String> passwordObservable = WidgetObservable.text(password)
            .map(InputValidation::stringOfTextChangEvent);
    Observable<String> confirmedPasswordObservable = WidgetObservable.text(confirmedPassword)
            .map(InputValidation::stringOfTextChangEvent);
......

将三个输入流合并成一个,并根据验证结果设置提交按钮的状态。

1
2
3
4
5
6
7
8
9
10
11
......
        Observable<InputValidation> inputValidationObservable = Observable.combineLatest(
                userNameObservable,
                passwordObservable,
                confirmedPasswordObservable,
                InputValidation::new
        ).map(inputValidation -> {
            submitButton.setEnabled(inputValidation.allowSubmit());
            return inputValidation;
        });
......

combineLatest 的最后一个参数是个合并方法,我传入了个 Java8 支持的构造器引用。

1
2
3
4
5
6
......
        ViewObservable.clicks(submitButton).zipWith(
                inputValidationObservable,
                (onClickEvent, inputValidation) -> inputValidation
        ).subscribe();
......

最终结果:

submit

在上面的代码中,起初调用的是 combineLatest,导制每次输入时,都会触发最终的 Observable,最后改用 zip 来合并,因为 zip 只有新的流出现,才会被触发,而 combineLatest 只要其中任意一个流有新数据,都会被触发,看图比较直观。

zip

zip

combineLatest

combineLatest

用 RxJava 来写 App,代码更加清晰,大大降低了耦合度,使分离的逻辑形成链,让上帝的归上帝,凯撒的归凯撒,感觉就是一个字~~酸爽!