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<Boolean> 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<Boolean> 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.