Observable.share()

I've been burned by the share() operator.

I was implementing a login form validation using RxJava and I had to share an Observable to multiple observers. Here is what I had at the beginning:

// 1. Observable that emits true if the email address is valid.
Observable emailValid = RxTextView.textChanges(emailTextView)
        .map(VALIDATE_EMAIL)
        .share();

// 2. Show an error on the email field if the email is invalid and it's not focused
Observable.combineLatest(emailValid, RxView.focusChanges(emailTextView),
        (emailIsValid, hasFocus) -> emailIsValid || hasFocus)
        .subscribe(emailErrorAction);

// 3. Enable the submit button if both the email and password are valid.
Observable.combineLatest(emailValid, passwordValid,
            (emailValid, passwordValid) -> emailValid && passwordValid)
            .subscribe(enableSubmitButton));

Except I had an issue, the submit button would start off enabled (even though both the email field and the password fields were empty). In order to disable the submit button I had to enter text into both the email field and the password field.

Weird.

It turns out there is a small race condition caused by the use of share(). Since share() begins emitting items as soon as the first Observer subscribes Observable #3 doesn't receive the initial value of the emailTextView, but Observable #2 does.

To solve this we need to wait until all observers have subscribed before connecting to Observable #1.

// 1. Observable that emits true if the email address is valid.
ConnectableObservable emailValid = RxTextView.textChanges(emailTextView)
        .map(VALIDATE_EMAIL)
        .publish();

// 2. Show an error on the email field if the email is invalid and it's not focused
Observable.combineLatest(emailValid, RxView.focusChanges(emailTextView),
        (emailIsValid, hasFocus) -> emailIsValid || hasFocus)
        .subscribe(emailErrorAction);

// 3. Enable the submit button if both the email and password are valid.
Observable.combineLatest(emailValid, passwordValid,
            (emailValid, passwordValid) -> emailValid && passwordValid)
            .subscribe(enableSubmitButton));

emailValid.connect()

Now Observable #3 gets the initial value of the emailValid Observable and the button is enabled/disabled properly.

TL;DR

Only use share() when you don't know how many subscribers you might have and you don't care if some subscribers miss values.

See