Search code examples
androidrx-javarx-androidandroid-mvprx-binding

Why does only the latest Subscriber get onNext Event in RxJava on Android using Kotlin and RxMVP


Given following scenario:

A View featuring a CheckBox and multiple EditTexts
The View offers access to the Observables using Jake Wharton's RxBinding like so

fun observeUsername(): InitialValueObservable<CharSequence> = RxTextView.textChanges(et_username)

for the EditTexts (there three of them, for Username, Password and Email) and

fun observeSignUpCheckBox(): InitialValueObservable<Boolean> = RxCompoundButton.checkedChanges(cb_sign_up)

for the CheckBox

The Presenter features one method per EditText which is as simple as

fun observeUsernameText(): Disposable {
    return view.observeUsernameText()
            .skipInitialValue()
            .map { username -> StringUtils.isValidUsername(username.toString()) }
            .subscribe({ view.setValidUsername(it) })
}

and one method for the CheckBox:

fun observeSignUpCheckBox(): Disposable {
    return view.observeSignUpCheckBox()
            .subscribe({ checked ->
                Timber.d("### view trigger")
            })
}

all of these methods are called in the onCreate of the Presenter and everything worked as expected.

Now the Problem:
I added a new function in the Presenter that validates the users input:

fun observeInputFields(): Disposable {
    val email = view.observeEmailText().map { email -> StringUtils.isValidEmail(email.toString()) }
    val password = view.observePasswordText().map { password -> StringUtils.isValidPassword(password.toString()) }
    val signUp = view.observeSignUpCheckBox()
    val username = view.observeUsernameText().map { username -> StringUtils.isValidUsername(username.toString()) }
    return Observable.combineLatest(email, password, signUp, username,
            Function4<Boolean, Boolean, Boolean, Boolean, Boolean> { validEmail, validPassword, signUpChecked, validUsername ->
                validEmail && validPassword && (!signUpChecked || signUpChecked && validUsername)
            })
            .subscribe({ validForm ->
                Timber.d("### form trigger")
                view.enableContinue(validForm)
            })
}

Whenever I change the content of an EditText both Subscriptions (the one for the EditText and the combined one added in observeInputFields) receive the event as expected.
But, if I tap on the CheckBox only the last Subscription receives the event depending on the order of the functions in the onCreate.

fun onCreate() {
    // here the logs only show '### form trigger'
    disposables.add(observeSignUpCheckBox())
    disposables.add(observeInputFields())
    // ... omitted for clarity
}

or

fun onCreate() {
    // here the logs only show '### view trigger'
    disposables.add(observeInputFields())
    disposables.add(observeSignUpCheckBox())
    // ... omitted for clarity
}

I cannot figure out why this strange thing is happening only for the CheckBox but not for the EditTexts. This is highly confusing...

Any help is greatly appreciated as I am currently stuck on this :(

Thanks!


Solution

  • If you check the source code of the library here and here, you will see that what you are describing is expected behaviour. For aCompoundButton you can have only one Observable at a time but for aTextView multiple.

    If you want to go even further you can see that here to create the Observable you need to use the setOnCheckedChangeListener on the CompoundButton which replaces the possible existing listener every time.

    On the other hand, for TextView you can add multiple TextWatchers and remove each one individually onDispose.