Note that the definitive article on this subject is Don't break the chain: use RxJava's
compose()
operator by Android programmer extraordinaire Dan Lew. You're better of reading that article instead of this one, but I am including this item for completeness.
In the Khan Academy Android app, we make extensive use of the Optional
class from Google's Guava library. Whereas we typically represent "absent values" by using null
, the Optional
class encodes the optionality of such values in the type system. This helps ensure that clients deal with such optional values in a direct manner. For example:
final Optional<String> optional = Optional.of("abc");
if (optional.isPresent()) {
final String s = optional.get();
}
The code above first creates an Optional
instance with the string "abc"
. Because that instance contains a value, we say that the value is present, not absent. Its isPresent
method therefore returns true
, and the call to get
assigns the value "abc"
to s
.
Our application has many cases where an Observable
emits Optional<T>
for some type T
, and downstream subscribers want to receive only the present values of type T
.
For example, a BookmarkEvent
instance represents any update to the user's bookmarks, such as adding a bookmark, removing a bookmark, or updating the download progress associated with a bookmark. Every BookmarkEvent
instance has an Optional
value of type DownloadEvent
that is accessed through its downloadEventOptional()
method. If the BookmarkEvent
updates the download progress of a bookmark, then that Optional<DownloadEvent>
contains a DownloadEvent
instance with more details, such as how many bytes have been downloaded so far, what the estimated total byte count is, and so on. We can use this DownloadEvent
to display a notification with the download progress:
mBookmarkManager.getBookmarkEventObservable()
.map(bookmarkEvent -> bookmarkEvent.downloadEventOptional())
.filter(downloadEventOptional -> downloadEventOptional.isPresent())
.map(downloadEventOptional -> downloadEventOptional.get());
.subscribe(downloadEvent -> {
mNotificationManager.displayNotificationForDownloadEvent(downloadEvent);
});
}
The filter
operator and its following map
operator consume an Optional<DownloadEvent>
, and together emit only present DownloadEvent
instances. Again, this is a common occurrence, but for different types. And so we might define a utility method like so:
/**
* @return an observable emitting the present values of {@link Optional} instances emitted by
* the observable parameter
*/
public static <T> Observable<T> observePresentValues(Observable<Optional<T>> observable) {
return observable.
.filter(optional -> optional.isPresent())
.map(optional -> optional.get());
}
And then call it like so:
final Observable<Optional<DownloadEvent>> downloadEventOptionalObservable =
mBookmarkManager.getBookmarkEventObservable()
.map(bookmarkEvent -> bookmarkEvent.downloadEventOptional())
ObservableUtils.observePresentValues(downloadEventOptionalObservable)
.subscribe(downloadEvent -> {
mNotificationManager.displayNotificationForDownloadEvent(downloadEvent);
});
There are other ways to structure this, but none of them are pleasing. Reading RxJava is arguably most straightforward when there are no temporary variables like downloadEventOptionalObservable
, and following the left margin from top to bottom yields all operators that constitute the subscriber chain.
To achieve these goals, we can use the compose
method of Observable
. This method accepts an instance that implements Transformer<F, R>
, which transforms an instance of Observable<F>
to an instance of Observable<R>
. The above calls to filter
and then map
specify a transformation from Observable<Optional<T>>
to Observable<T>
. We can instead define a utility method that returns a Transformer
:
/**
* @return a transformer for emitting the present values of emitted {@link Optional} instances
*/
public static <T> Transformer<Optional<T>, T> presentValuesTransformer() {
return observable -> observable
.filter(optional -> optional.isPresent())
.map(optional -> optional.get());
}
And then pass it to compose
:
mBookmarkManager.getBookmarkEventObservable()
.map(bookmarkEvent -> bookmarkEvent.downloadEventOptional())
.compose(ObservableUtils.presentValuesTransformer())
.subscribe(downloadEvent -> {
mNotificationManager.displayNotificationForDownloadEvent(downloadEvent);
});
}
This is much more readable than using the observePresentValues
utility method.
Note that the Transformer<Optional<T>, T>
instance returned has no state, and is therefore trivially immutable and safe for sharing. Moreover its sequence of filter
and map
operators does not rely on the parameterized type T
. The same instance, therefore, can transform a Observable<Optional<T>>
for any type T
.
To implement this, we create a Transformer<Optional<Object>, Object>
instance that is both private and static, and then modify presentValuesTransformer
to return it:
private static Transformer PRESENT_VALUES_TRANSFORMER =
new Transformer<Optional<Object>, Object>() {
@Override
public Observable<Object> call(final Observable<Optional<Object>> optionalObservable) {
return optionalObservable
.filter(optional -> optional.isPresent())
.map(optional -> optional.get());
}
};
@SuppressWarnings("unchecked")
public static <T> Transformer<Optional<T>, T> presentValuesTransformer() {
return PRESENT_VALUES_TRANSFORMER;
}
The compiler cannot conclude that the cast from Transformer<Optional<Object>, Object>
to Transformer<Optional<T>, T>
is safe, and so it generates a compilation warning. The SuppressWarnings("unchecked")
annotation suppresses that warning.