Michael Parker - Leanpubsamples.leanpub.com/Effectiverxjava-sample.pdfCONTENTS 1 EffectiveRxJava...

47
Effective RxJava Michael Parker

Transcript of Michael Parker - Leanpubsamples.leanpub.com/Effectiverxjava-sample.pdfCONTENTS 1 EffectiveRxJava...

Page 1: Michael Parker - Leanpubsamples.leanpub.com/Effectiverxjava-sample.pdfCONTENTS 1 EffectiveRxJava Thisisacollectionofitems,eachspecifyingonegeneralrule,tohelpyouwriteRxJava¹codemore

Effective RxJava

Michael Parker

Page 2: Michael Parker - Leanpubsamples.leanpub.com/Effectiverxjava-sample.pdfCONTENTS 1 EffectiveRxJava Thisisacollectionofitems,eachspecifyingonegeneralrule,tohelpyouwriteRxJava¹codemore

Effective RxJava

Michael Parker

© 2016 Michael Parker

Page 3: Michael Parker - Leanpubsamples.leanpub.com/Effectiverxjava-sample.pdfCONTENTS 1 EffectiveRxJava Thisisacollectionofitems,eachspecifyingonegeneralrule,tohelpyouwriteRxJava¹codemore

Contents

Effective RxJava . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1

Page 4: Michael Parker - Leanpubsamples.leanpub.com/Effectiverxjava-sample.pdfCONTENTS 1 EffectiveRxJava Thisisacollectionofitems,eachspecifyingonegeneralrule,tohelpyouwriteRxJava¹codemore

CONTENTS 1

Effective RxJava

This is a collection of items, each specifying one general rule, to help you write RxJava¹ code moreeffectively. It is modeled after two of my favorite technical books, Effective C++ and Effective Java.I appreciate not only the soundness of their contents, but the brevity of their writing style. I hopethat Effective RxJava has the same value proposition.

For each rule I’ve attempted to provide relevant example code – most of which is currently inproduction in the Khan Academy Android app².

Items

• Use Retrolambda³• Emit immutable values⁴• Understand Observable and observer chains⁵• Convert functions to Observable⁶• Convert callbacks to Observable⁷• Understand subscribeOn and observeOn⁸• Understand switchMap⁹• Test emitted values using TestObserver¹⁰• Test work scheduling using TestScheduler¹¹• Use compose for operator sequences¹²• Optimize your subscribers¹³

Use Retrolambda

Retrolambda¹⁴ enables lambda expressions and method references on Android by transforming Java8 bytecode to bytecode for Java 7 and lower. While this may sound scary, lambda expressionsand method references remove the syntactic boilerplate from the anonymous classes you mustdefine when providing Func and Action implementations, thereby greatly improving your code’sreadability. Additionally, the Gradle plugin¹⁵ makes integrating Retrolambda with your build

¹https://github.com/ReactiveX/RxJava²https://play.google.com/store/apps/details?id=org.khanacademy.android³items/use-retrolambda.md⁴items/emit-immutable-values.md⁵items/understand-observable-and-observer-chains.md⁶items/convert-functions-to-observable.md⁷items/convert-callbacks-to-observable.md⁸items/understand-subscribeon-and-observeon.md⁹items/understand-switch-map.md¹⁰items/test-emitted-values-using-testobserver.md¹¹items/test-work-scheduling-using-testscheduler.md¹²items/use-compose-for-operator-sequences.md¹³items/optimize-your-subscribers.md¹⁴https://github.com/orfjackal/retrolambda¹⁵https://github.com/evant/gradle-retrolambda

Page 5: Michael Parker - Leanpubsamples.leanpub.com/Effectiverxjava-sample.pdfCONTENTS 1 EffectiveRxJava Thisisacollectionofitems,eachspecifyingonegeneralrule,tohelpyouwriteRxJava¹codemore

CONTENTS 2

seamless.

Without Retrolambda, your RxJava code will look like:

mContentDatabase

.fetchContentItems(ImmutableSet.of(contentItemId))

.map(new Func1<Map<ContentItemIdentifier, ContentItem>, ContentItem>() {

@Override

public ContentItem call(final Map<ContentItemIdentifier, ContentItem> re\

sultSet) {

return checkNotNull(resultSet.get(contentItemId));

}

})

.map(new Func1<ContentItem, TopicPath>() {

@Override

public TopicPath call(final ContentItem contentItem) {

return contentItem.topicPath();

}

})

.subscribe(new Action1<TopicPath>() {

@Override

public void call(final TopicPath topicPath) {

openContentViewActivity(contentItemId, topicPath);

}

});

With Retrolambda, your RxJava code will look like:

mContentDatabase

.fetchContentItems(ImmutableSet.of(contentItemId))

.map(resultSet -> checkNotNull(resultSet.get(contentItemId)))

.map(ContentItem::topicPath)

.subscribe(topicPath -> {

openContentViewActivity(contentItemId, topicPath);

});

We at Khan Academy¹⁶ have been using it in our Android application¹⁷ with no issues. Many othercompanies use it with their own applications. Use it with your own application if you are usingRxJava.

¹⁶https://www.khanacademy.org/¹⁷https://play.google.com/store/apps/details?id=org.khanacademy.android

Page 6: Michael Parker - Leanpubsamples.leanpub.com/Effectiverxjava-sample.pdfCONTENTS 1 EffectiveRxJava Thisisacollectionofitems,eachspecifyingonegeneralrule,tohelpyouwriteRxJava¹codemore

CONTENTS 3

Emit immutable values

The ReactiveCocoa¹⁸ project, which is an Functional Reactive Programming implementation for iOSand OS X, touts itself as “streams of values over time.” We propose that you go one step further, andto emit “streams of immutable values over time” whenever possible. To quote from the AndroidDevelopment Best Practices guide¹⁹ for Khan Academy²⁰:

Our principal goal as programmers is to reduce complexity. At any point in a program,we can reason about the state of a constant trivially – its state is the state uponassignment. By contrast, the state of a variable can change endlessly.

With judicious use, immutable objects can lead to quicker execution speed and lowermemory usage. The hash value of a String, the query parameters of an immutable URL,and the distance traced by an immutable sequence of points are all immutable as well.We can cache their values for later use instead of recompute them on every access.

An immutable value is safe in many ways. We can share an immutable value withclients without worry that the client will silently mutate the value. Similarly, a clientcan accept an immutable value as a parameter without needing to defensively copyit. An immutable value is also thread-safe, because a client must acquire a lock onlyto read data that another client can concurrently modify. An immutable value rendersthat moot.

To summarize: Immutable values help tame complexity. In RxJava, a data source with an Observableshould emit immutable values that reflect its current state. When the state of the data source isupdated, it should simply emit a new immutable value that reflects the updated state.

If you are developing an Android application, and you are using ProGuard²¹ to eliminate dead codefrom your application, then consider using AutoValue and Guava from Google to help constructimmutable values. (AutoValue includes Guava as a dependency, and Guava is a large library. Sodead code elimination via ProGuard is ideal.)

Use AutoValue

AutoValue²² creates immutable value types with very little effort required on your part. From itsdocumentation:

Classes with value semantics are extremely common in Java. These are classes for whichobject identity is irrelevant, and instances are considered interchangeable based only onthe equality of their field values…AutoValue provides an easier way to create immutablevalue types, with less code and less room for error.

¹⁸https://github.com/ReactiveCocoa/ReactiveCocoa/¹⁹https://github.com/Khan/style-guides/blob/master/style/android-development-best-practices.md²⁰https://www.khanacademy.org/²¹http://proguard.sourceforge.net/²²https://github.com/google/auto/tree/master/value

Page 7: Michael Parker - Leanpubsamples.leanpub.com/Effectiverxjava-sample.pdfCONTENTS 1 EffectiveRxJava Thisisacollectionofitems,eachspecifyingonegeneralrule,tohelpyouwriteRxJava¹codemore

CONTENTS 4

Instances of immutable value types defined by AutoValue are simply data. They have no behavior.This is ideal because such added behavior will seek to mutate the associated value type instance,and we want to avoid this.

To use AutoValue, create an abstract class with an abstract getter for each desired field. For example,our ArticleViewFragment for viewing articles defines a static inner class named ToolbarViewData

that is annotated with @AutoValue:

@AutoValue

static abstract class ToolbarViewData {

abstract Article article();

abstract ContentItemThumbnailData thumbnailData();

}

Upon compiling, the annotation processor for AutoValue creates a concrete subclass of Tool-

barViewData named AutoValue_ArticleViewFragment_ToolbarViewData. This class has a construc-tor that is initialized with an Article and ContentItemThumbnailData instance. These values areassigned to private and final instance fields, which the implemented article and thumbnailData

getters return:

@Override

Article article() {

return article;

}

@Override

ContentItemThumbnailData thumbnailData() {

return thumbnailData;

}

The class also implements equals and hashCode, making such values suitable for use in sets, or askeys in maps:

@Override

public boolean equals(Object o) {

if (o == this) {

return true;

}

if (o instanceof ArticleViewFragment.ToolbarViewData) {

ArticleViewFragment.ToolbarViewData that = (ArticleViewFragment.ToolbarV\

iewData) o;

return (this.article.equals(that.article()))

Page 8: Michael Parker - Leanpubsamples.leanpub.com/Effectiverxjava-sample.pdfCONTENTS 1 EffectiveRxJava Thisisacollectionofitems,eachspecifyingonegeneralrule,tohelpyouwriteRxJava¹codemore

CONTENTS 5

&& (this.thumbnailData.equals(that.thumbnailData()));

}

return false;

}

@Override

public int hashCode() {

int h = 1;

h *= 1000003;

h ^= article.hashCode();

h *= 1000003;

h ^= thumbnailData.hashCode();

return h;

}

Moreover, it also implements the toString method, which facilitates with logging such values:

@Override

public String toString() {

return "ToolbarViewData{"

+ "article=" + article + ", "

+ "thumbnailData=" + thumbnailData

+ "}";

}

Because AutoValue_ArticleViewFragment_ToolbarViewData is not a convenient name, conventionis to add a static factory method named create to each class annotated with @AutoValue. Thismethod instantiates and returns the corresponding AutoValue implementation. For example, Tool-barViewData defines:

public static ToolbarViewData create(final Article article,

final ContentItemThumbnailData thumbnailDat\

a) {

return new AutoValue_ArticleViewFragment_ToolbarViewData(article, thumbnailD\

ata);

}

A client can now simply call static factory method create of ToolbarViewData to create such aAutoValue_ArticleViewFragment_ToolbarViewData instance.

Other conveniences from AutoValue include:

Page 9: Michael Parker - Leanpubsamples.leanpub.com/Effectiverxjava-sample.pdfCONTENTS 1 EffectiveRxJava Thisisacollectionofitems,eachspecifyingonegeneralrule,tohelpyouwriteRxJava¹codemore

CONTENTS 6

• The generated class ensures that each constructor parameter is not null, unless the corre-sponding getter in the abstract base class has the @Nullable annotation.

• A builder is automatically generated through the use of the @AutoValue.Builder annotation.• The generated class is serializable if the abstract base class implements Serializable.

See the AutoValue documentation²³ for more details.

If you are developing for Android, consider integrating the Parcelable extension for AutoValue²⁴.As its name suggests, it generates value types that implement the Parcelable interface. This allowsyou to easily persist instances in a Bundle, which may be useful when specifying Intent parametersor saving the state of an activity or fragment.

Use Guava

Guava²⁵ contains several of Google’s core libraries for its Java based projects.Most notably it includesimmutable collection implementations²⁶, thereby allowing you to construct immutable List, Map,Set, SortedMap, and SortedSet instances. This is possible because the mutating methods of eachcollection interface, such as add, set, or remove, are defined as optional operations. The underlyingimplementation can choose to implement the method as specified by the interface, or throw anUnsupportedOperationException. Guava chooses the latter.

If an Observable must emit a collection, use Guava to construct and then emit an immutable copyof the backing collection. For example:

private void emitUpdatedFiles() {

final Set<File> updatedFiles = ImmutableSet.copyOf(mDownloadsByFile.keySet()\

);

mDownloadsSubject.onNext(updatedDownloads);

}

If entries are later added to or removed from mDownloadsByFile, those changes are not reflected inthe emitted Set because an immutable copy was made.

These classes are also useful for ensuring that AutoValue instances are constructed with immutablecollections, and thus the AutoValue instance itself is also immutable. For example:

²³https://github.com/google/auto/tree/master/value²⁴https://github.com/rharter/auto-value-parcel²⁵https://github.com/google/guava²⁶https://github.com/google/guava/wiki/ImmutableCollectionsExplained

Page 10: Michael Parker - Leanpubsamples.leanpub.com/Effectiverxjava-sample.pdfCONTENTS 1 EffectiveRxJava Thisisacollectionofitems,eachspecifyingonegeneralrule,tohelpyouwriteRxJava¹codemore

CONTENTS 7

@AutoValue

public abstract class ProcessedConversionsError {

public abstract List<String> failedConversionNames();

public abstract List<String> invalidConversionNames();

public static ProcessedConversionsError create(

List<String> failedConversionNames,

List<String> invalidConversionNames) {

return new AutoValue_ProcessedConversionsError(

ImmutableList.copyOf(failedConversionNames),

ImmutableList.copyOf(invalidConversionNames)

);

}

}

If the caller mutates the parameters argument that was passed into the constructor, those changesare not reflected in the ProcessedConversionsError instance, again because an immutable copywas made.

Finally, note that if the value passed into a copyOf method is itself an instance of the immutablecollection, then the copyOf method simply returns its parameter. See the Guava documentation onimmutable collections²⁷ for more details.

Other sources of immutability

Well written third party libraries often have immutable abstractions that are ripe for reuse. Forexample, if you are using OkHttp²⁸, then you can use its HttpUrl class to represent an immutableURL. Moreover, OkHttp includes Okio²⁹ as a dependency, which has a ByteString class that youcan use to represent an immutable sequence of bytes.

Peruse the API documentation of third party libraries that you use, and identify immutable valuetypes that you can leverage.

Understand Observable and observer chains

Chains of Observable instances and observers are important. Understanding them is the key tounderstanding how your RxJava code executes.

To begin our understanding, let us start with the most banal of examples:

²⁷https://github.com/google/guava/wiki/ImmutableCollectionsExplained²⁸http://square.github.io/okhttp/²⁹https://github.com/square/okio

Page 11: Michael Parker - Leanpubsamples.leanpub.com/Effectiverxjava-sample.pdfCONTENTS 1 EffectiveRxJava Thisisacollectionofitems,eachspecifyingonegeneralrule,tohelpyouwriteRxJava¹codemore

CONTENTS 8

Observable.just(1, 2, 3, 4, 5)

.filter(x -> (x % 2) == 1)

.map(x -> x * x)

.subscribe(integer -> System.out.println("Received value: " + integer));

When run, this prints:

Received value: 1

Received value: 9

Received value: 25

Now most newcomers to RxJava intuitively understand why these values are printed. They haveseen filter and map in other contexts and in other languages, and so they can understand how theinteger values are filtered and then transformed.

However, just as I did, they may fail to understand why code like the following does nothing:

mBookmarkDatabase.addBookmark(contentItemId);

The addBookmarkmethod returns an Observable<Boolean> that emits true if a new bookmark wasadded for the content item with the given identifier, or emits false if such a bookmark alreadyexisted. (This is similar to the add method of Collection returning true if the collection waschanged.) But in this case, we do not care about which value is emitted, and so the returnedObservable is not assigned to a value.

But the statement above does not write the bookmark to the database if it does not already exist.This is because the returned Observable is not subscribed to, either directly or through a chain ofother Observable instances.

Again, to understand how RxJava code executes, you must understand this chain.

Creating an Observable chain

Factory methods like just and filter and map above return Observable<T> instances. Whereasjust is a static factory method, filter and map are instance factory methods invoked on anexisting Observable instance. These two categories of factory methods have different state, differentbehavior, and a different purpose:

• Typically, an Observable created from a static factory has no upstream Observable instance.It is the root of any chain. It is constructed with some “work” to perform, which we discussbelow.

Page 12: Michael Parker - Leanpubsamples.leanpub.com/Effectiverxjava-sample.pdfCONTENTS 1 EffectiveRxJava Thisisacollectionofitems,eachspecifyingonegeneralrule,tohelpyouwriteRxJava¹codemore

CONTENTS 9

• An Observable created from an instance factory method has an upstream Observable

instance. This upstream Observable instance is the instance on which the factory methodwas invoked. It is an intermediate or end Observable of any chain. It is constructed withsome logic to filter or transform emitted values, which we discuss below.

Let us rewrite our most banal of examples to include some intermediate variables for the Observableinstances:

Observable<Integer> o1 = Observable.just(1, 2, 3, 4, 5);

Observable<Integer> o2 = o1.filter(x -> (x % 2) == 1);

Observable<Integer> o3 = o2.map(x -> x * x);

We can diagram this as:

Chain of Observable instances

And now let us dissect what happens when the client invokes subscribe on o3.

Creating an observer chain

We often talk about subscribing to Observable instances, and we talk about Observable instancesfiltering or transforming values. But to increase our understanding at the risk of being completelypedantic, a client that calls subscribe on an Observable isn’t really subscribing to the Observable

Page 13: Michael Parker - Leanpubsamples.leanpub.com/Effectiverxjava-sample.pdfCONTENTS 1 EffectiveRxJava Thisisacollectionofitems,eachspecifyingonegeneralrule,tohelpyouwriteRxJava¹codemore

CONTENTS 10

instance itself. As we discuss below, calling subscribe on an Observable creates a parallel chain ofobservers. It is along that chain of observers that values are emitted, and then filtered or transformed.

Above, when the client invokes subscribe on o3, it registers itself as the most downstream observeron a chain of observers that will be implicitly created:

Observer subscribing to o3

o3 then implicitly calls subscribe on its upstream Observable instance, which is o2. To thissubscribe method it passes an observer implementation that propagates all events to the observerthat the client passed to the subscribe method of o3. But the observer implementation providedby o3 also implements the behavior that o3 was constructed with. Namely, the observer squares avalue before propagating it. You can think of its onNext method as similar to:

void onNext(T value) {

// func is x -> x * x, provided on construction of o3

final T transformedValue = func.call(value);

downstreamObserver.onNext(transformedValue);

}

We can diagram this as:

Page 14: Michael Parker - Leanpubsamples.leanpub.com/Effectiverxjava-sample.pdfCONTENTS 1 EffectiveRxJava Thisisacollectionofitems,eachspecifyingonegeneralrule,tohelpyouwriteRxJava¹codemore

CONTENTS 11

Observer subscribing to o2

Again, o3 implicitly calls subscribe on o2. This repeats a similar process, where o2 implicitly callssubscribe on o1. o2 passes to this method an observer implementation that only propagates valuesthat are odd, which again is the behavior that it was constructed with. You can think of the onNextmethod of this observer as similar to:

void onNext(T value) {

// func is x -> (x % 2) == 1, provided on construction of o2

final boolean isSatisfied = func.call(value);

if (isSatisfied) {

downstreamObserver.onNext(value);

}

}

We can diagram this as:

Page 15: Michael Parker - Leanpubsamples.leanpub.com/Effectiverxjava-sample.pdfCONTENTS 1 EffectiveRxJava Thisisacollectionofitems,eachspecifyingonegeneralrule,tohelpyouwriteRxJava¹codemore

CONTENTS 12

Observer subscribing to o1

Again, o2 implicitly calls subscribe on o1. But note that o1 has no upstream Observable instancethat it can invoke subscribe on. Instead, it is this call that completes the observer chain and beginsthe emission of events.

Emitting values on an observer chain

While o3 and o2 are configured to transform and filter values, respectively, o1 is configured to emitvalues. When its subscribemethod is invoked with an observer implementation, it emits the valuesthat it is constructed with to that observer. More generally, the Observable that is the root in achain of Observable instances is configured to perform some work upon its subscribe methodbeing invoked. This work typically constitutes creating a side-effect that leads to the emission ofzero or more values, followed by the delivery of the completion event.

Now we understand why the previous use of addBookmark did not actually write to the database:The side-effect of writing to the database only happens when subscribe is invoked on the returnedObservable. By calling subscribe, this work is performed:

mBookmarkDatabase.addBookmark(contentItemId)

.subscribe(didNotExist -> { /* success */ });

Returning to our banal example, the “work” of o1 is to emit all values to its observer, followed by acompletion event. You can think of its subscribe method as executing the following:

Page 16: Michael Parker - Leanpubsamples.leanpub.com/Effectiverxjava-sample.pdfCONTENTS 1 EffectiveRxJava Thisisacollectionofitems,eachspecifyingonegeneralrule,tohelpyouwriteRxJava¹codemore

CONTENTS 13

for (T element : list) {

observer.onNext(element);

}

observer.onCompleted();

These events propagate down the observer chain, where the observer created by o2 filters the values,and then the observer created by o3 squares the values. Those values are then passed to the methodpassed to the subscribe method of o3, which prints them.

Unsubscribing

Note that in our banal example above, the subscribe method of o1 immediately emitted all valuesto its observer and then it completed. That is, the work of the Observable instance happenedimmediately.

But other Observable instances might define work that takes time to complete or is scheduled forlater execution, such as making a network request and emitting the response, or writing data to diskon another thread. In such cases, the time for the Observable to complete its work might exceed thelifetime of the client that invoked subscribe. In many cases, we do not want to extend the lifetimeof the client so that it observes the completion of the work. Instead, we want to unsubscribe ourobserver from the chain of observers.

To do this, we retain the Subscripton instance returned by the subscribe method of Observable:

Subscription s = o3.subscribe(integer -> System.out.println("Received value: " +\

integer));

If the client later invokes unsunscribe on this Subscription instance, then superficially the observerpassed to the subscribe method of o3 no longer receives any events.

To understand what happens implicitly, note that not only does each observer in the observer chaintrack the downstream observer that it will propagate events to, but each observer refers to theupstream observer that it will receive events from. Each implicitly created observer can unsubscribefrom its upstream observer in turn:

• The client invokes s.unsubscribe(), which unsubscribes the client from the squaring ob-server implicitly created by calling subscribe on o3.

• This squaring observer then implicitly unsubscribes from the filtering observer implicitlycreated by calling subscribe on o2.

• This filtering observer then implicitly unsubscribes from the source o1.

The call to unsubscribe therefore destroys the chain of observers that was implicitly created by thecall to subscribe.

Page 17: Michael Parker - Leanpubsamples.leanpub.com/Effectiverxjava-sample.pdfCONTENTS 1 EffectiveRxJava Thisisacollectionofitems,eachspecifyingonegeneralrule,tohelpyouwriteRxJava¹codemore

CONTENTS 14

Going forward

You should now have a mental model for how Observable instances form chains, and how observerchains are formed by the Observable instances and propagate events. Later, using this model, wewill explore the difference between subscribeOn and observeOn, and discuss the difference betweenhot and cold observables.

Convert functions to Observable

From the Javadoc for Observable, it is not immediately clear how to create an Observable thatinvokes a given Func0 upon subscription. We can accomplish this by combining its defer and just

operators:

/**

* @return an {@link Observable} that emits invokes {@code function} upon subscr\

iption and emits

* its value

*/

public static <O> Observable<O> makeObservable(final Func0<O> function) {

checkNotNull(function);

return Observable.defer(() -> Observable.just(function.call()));

}

Sometimes this method is useful by itself:

mVideoDownloadManager.getDownloadedJsonTranscriptUri(videoItemId)

.flatMap(downloadedJsonTranscriptUriOptional -> {

if (downloadedJsonTranscriptUriOptional.isPresent()) {

// Read the transcript from the downloaded file.

return ObservableUtils.makeObservable(() -> {

final URI uri = downloadedJsonTranscriptUriOptional.get();

final BufferedReader reader = FileUtil.newUtf8Reader(new File(ur\

i));

return VideoSubtitleSequenceJsonDecoder.read(new JsonReader(read\

er));

})

.subscribeOn(Schedulers.io());

} else {

// Return the transcript from the network using Retrofit.

return mContentApi.downloadVideoSubtitles(youTubeId);

}

Page 18: Michael Parker - Leanpubsamples.leanpub.com/Effectiverxjava-sample.pdfCONTENTS 1 EffectiveRxJava Thisisacollectionofitems,eachspecifyingonegeneralrule,tohelpyouwriteRxJava¹codemore

CONTENTS 15

})

// more operators follow here ...

In many cases, we have found this method useful when defining a class that wraps another classand converts its public API to expose an Observable for each public method. For example, wehave a ContentDatabase in the Khan Academy Android app³⁰ through which the client can loadcontent, such as videos and articles and their parent topics (e.g. “math” and “3rd grade math”), froma database on the device. Its methods are synchronous:

public interface ContentDatabase extends Closeable {

Optional<Topic> fetchTopic(String topicSlug);

Optional<ContentItem> fetchContentItem(ContentItemIdentifier identifier);

// more methods follow here ...

}

The synchronous API of ContentDatabase makes testing an implementation easy:

@Test

public void testFetchMissingContentItem() {

final ContentItemIdentifier missingContentItemId = TestUtil.randomContentIte\

mId();

final Optional<ContentItem> contentItemOptional =

mContentDatabase.fetchContentItem(missingContentItemId);

assertEquals(Optional.absent(), contentItemOptional);

}

But our application should not invoke methods on ContentDatabase directly, as the main threadwill block upon reading from the database. We therefore introduce an ObservableContentDatabase

class that converts the return type of each method of ContentDatabase to an Observable. AObservableContentDatabase instance delegates to its underlying ContentDatabase instance, butinvokes each method on a given Scheduler so that all reads from the database happen off the mainthread:

³⁰https://play.google.com/store/apps/details?id=org.khanacademy.android

Page 19: Michael Parker - Leanpubsamples.leanpub.com/Effectiverxjava-sample.pdfCONTENTS 1 EffectiveRxJava Thisisacollectionofitems,eachspecifyingonegeneralrule,tohelpyouwriteRxJava¹codemore

CONTENTS 16

public class ObservableContentDatabase implements Closeable {

private final ContentDatabase mContentDatabase;

private final Scheduler mScheduler;

public ObservableContentDatabase(final ContentDatabase contentDatabase) {

this(contentDatabase, SchedulerUtils.newSingleThreadIoScheduler());

}

public ObservableContentDatabase(final ContentDatabase contentDatabase,

final Scheduler scheduler) {

mContentDatabase = checkNotNull(contentDatabase);

mScheduler = checkNotNull(scheduler);

}

public Observable<Optional<Topic>> fetchTopic(final String topicSlug) {

return subscribeOnScheduler(() -> mContentDatabase.fetchTopic(topicSlug)\

);

}

public Observable<Optional<ContentItem>> fetchContentItem(

final ContentItemIdentifier contentItemId) {

return subscribeOnScheduler(() -> mContentDatabase.fetchContentItem(cont\

entItemId));

}

// more delegating methods follow here ...

private <T> Observable<T> subscribeOnScheduler(final Func0<T> function) {

return ObservableUtils.makeObservable(function)

.subscribeOn(mScheduler);

}

}

Each of the public methods that return an Observable uses the private method subscribeOn-

Scheduler to create that Observable. That method accepts a Func0 parameter specifying the workto execute on the Scheduler instance mScheduler. It uses our makeObservable method to convertthat work into an Observable, and uses the subscribeOn operator to ensure that it is executed onthe Scheduler instance mScheduler.

It up to a client, upon subscribing to the Observable instances from ObservableContentDatabase,to invoke observeOn(AndroidSchedulers.mainThread()) if necessary.

Convert callbacks to Observable

Many Java libraries define callback interfaces for operations that are very Observer-like:

Page 20: Michael Parker - Leanpubsamples.leanpub.com/Effectiverxjava-sample.pdfCONTENTS 1 EffectiveRxJava Thisisacollectionofitems,eachspecifyingonegeneralrule,tohelpyouwriteRxJava¹codemore

CONTENTS 17

• If the operation succeeded, an onSuccess or similar method provides a value.• If the operation failed, an onError or similar method provides a Throwable specifying theerror.

By converting such callbacks to Observable instances, we can apply operators to the emitted value.

As an example of such an Observer-like interface consider the ManifestCallback³¹ interface of classManifestFetcher in ExoPlayer³²:

/**

* @param <T> The type of manifest.

*/

public interface ManifestCallback<T> {

/**

* @param manifest the successfully loaded manifest

*/

void onSingleManifest(T manifest);

/**

* Invoked when the load has failed.

*

* @param e the cause of the failure for loading the manifest

*/

void onSingleManifestError(IOException e);

}

In the Khan Academy Android application³³, we use ExoPlayer to play videos. When the client hasan Internet connection, the video player streams videos using HLS³⁴. This allows the video playerto stream the video at a quality that is commensurate with its available bandwidth. The URLs forthe video streams of different qualities are specified by a HlsPlaylist instance. The client musttherefore first download this HlsPlaylist from the server.

Given the Uri instance of the playlist, we can retrieve it by constructing and invoking singleLoad

on a ManifestFetcher instance parameterized with type HlsPlaylist:

³¹http://google.github.io/ExoPlayer/doc/reference/com/google/android/exoplayer/util/ManifestFetcher.ManifestCallback.html³²http://google.github.io/ExoPlayer/³³https://play.google.com/store/apps/details?id=org.khanacademy.android³⁴https://en.wikipedia.org/wiki/HTTP_Live_Streaming

Page 21: Michael Parker - Leanpubsamples.leanpub.com/Effectiverxjava-sample.pdfCONTENTS 1 EffectiveRxJava Thisisacollectionofitems,eachspecifyingonegeneralrule,tohelpyouwriteRxJava¹codemore

CONTENTS 18

final UriDataSource dataSource = new DefaultUriDataSource(mContext, mUserAgent);

final HlsPlaylistParser parser = new HlsPlaylistParser();

final ManifestFetcher<HlsPlaylist> playlistFetcher = new ManifestFetcher<>(

uri.toString(), dataSource, parser);

playlistFetcher.singleLoad(mMainHandler.getLooper(), new ManifestCallback<HlsPla\

ylist>() {

@Override

public void onSingleManifest(final HlsPlaylist manifest) {

// code to configure ExoPlayer with the given playlist goes here ...

}

@Override

public void onSingleManifestError(final IOException e) {

// code to recover from the error goes here ...

}

});

When singleLoad either succeeds loading or fails to load the HlsPlaylist from the server, it invokesthe corresponding method of its ManifestCallback parameter, which is run on a Looper instanceassociated with the main thread.

To convert this to an Observable, we use the create static factory method:

final Observable<HlsPlaylist> playlistObservable = Observable.create(subscriber \

-> {

final UriDataSource dataSource = new DefaultUriDataSource(mContext, mUserAge\

nt);

final HlsPlaylistParser parser = new HlsPlaylistParser();

final ManifestFetcher<HlsPlaylist> playlistFetcher = new ManifestFetcher<>(

uri.toString(), dataSource, parser);

playlistFetcher.singleLoad(mMainHandler.getLooper(), new ManifestCallback<Hl\

sPlaylist>() {

@Override

public void onSingleManifest(final HlsPlaylist manifest) {

if (!subscriber.isUnsubscribed()) {

subscriber.onNext(manifest);

subscriber.onCompleted();

}

}

@Override

Page 22: Michael Parker - Leanpubsamples.leanpub.com/Effectiverxjava-sample.pdfCONTENTS 1 EffectiveRxJava Thisisacollectionofitems,eachspecifyingonegeneralrule,tohelpyouwriteRxJava¹codemore

CONTENTS 19

public void onSingleManifestError(final IOException e) {

if (!subscriber.isUnsubscribed()) {

subscriber.onError(e);

}

}

});

});

// subscribing to playlistObservable and configuring ExoPlayer follows here ...

Whenever a new subscription to the Observable is created, the code that was passed to the createmethod is invoked. This is a method that accepts as a parameter a Subscriber³⁵ instance belongingto the subscription.

When the method passed to create returns, no methods belonging to the Subscriber parameterhave yet been invoked. The fetching of the HlsPlaylist has begun, however, and when theManifestFetcher succeeds or fails to load the HlsPlaylist, it again invokes the correspondingmethod on its handler:

• If the operation succeeded, onSingleManifest is invoked with the playlist. This emits theHlsPlaylist instance as a single value to the subscriber using onNext, and then completesthe subscription using onCompleted.

• If the operation failed, an onSingleManifestError is invoked with the exception describingthe failure. This forwards the IOException to the subscriber using onError, thereby terminat-ing the subscription.

Note that if the client retains its Subscription instance, then it may call unsubscribe while theHlsPlaylist is still being fetched, and consequently before either method onSingleManifest oronSingleManifestError is invoked. Therefore both methods guard against the client unsubscribing.Note that unsubscribing does not cancel the work of fetching the HlsPlaylist, but instead, simplyignores the outcome.

Understand subscribeOn and observeOn

By default, Observable instances specify an execution policy of “immediate.” That is, if a threadhas an action to perform, then it is executed immediately by that thread. We can observe this in ourbanal example from the item Understand Observable and observer chains³⁶. To refresh our memory,that example is:

³⁵http://reactivex.io/RxJava/javadoc/rx/Subscriber.html³⁶understand-observable-and-observer-chains.md

Page 23: Michael Parker - Leanpubsamples.leanpub.com/Effectiverxjava-sample.pdfCONTENTS 1 EffectiveRxJava Thisisacollectionofitems,eachspecifyingonegeneralrule,tohelpyouwriteRxJava¹codemore

CONTENTS 20

Observable<Integer> o1 = Observable.just(1, 2, 3, 4, 5);

Observable<Integer> o2 = o1.filter(x -> (x % 2) == 1);

Observable<Integer> o3 = o2.map(x -> x * x);

o3.subscribe(integer -> System.out.println("Received value: " + integer));

We can observe the effects of the immediate scheduling policy by setting a breakpoint on the linewith subscribe and then running the program. When this breakpoint is reached the stack traceshows:

Stack trace for the observed value

Note that at the bottom of the stacktrace we find the call to subscribe on o3. The item UnderstandObservable and observer chains³⁷ explains in detail why this happens, but to summarize:

• The client explicitly calls subscribe on o3.• o3 then implicitly calls subscribe on o2.• o2 then implicitly calls subscribe on o1.

³⁷understand-observable-and-observer-chains.md

Page 24: Michael Parker - Leanpubsamples.leanpub.com/Effectiverxjava-sample.pdfCONTENTS 1 EffectiveRxJava Thisisacollectionofitems,eachspecifyingonegeneralrule,tohelpyouwriteRxJava¹codemore

CONTENTS 21

• The subscribe method of o1 then performs its work, by emitting each value that it wasconstructed with.

• The first value of 1 passes the filter operator, and then its value is squared. We observe thisvalue at the breakpoint.

This stacktrace gives a complete picture. We can follow the thread of execution from its call tosubscribe on o3 to each observed value.

But while this immediate execution policy is easy to reason about, it is not ideal when the workperformed by the root Observable instance is CPU-intensive or performs I/O. In such cases, thethread of execution can spend significant time computing the result or blocking on a read or writeoperation. And if the application is an Android application, then the thread of execution is likelythe main thread. Consequently the UI will freeze entirely and the user might have no choice but toforce-quit your unresponsive application.

To remedy these problems, we can introduce Scheduler instances via methods subscribeOn orobserveOn.

Choosing a scheduler

A Scheduler instance defines the thread on which actions should happen. If you are familiar withthe concurrency libraries from Java, you can think of a Scheduler as resembling an Executor³⁸. (Infact, the Schedulers class from RxJava³⁹ has a frommethod that adapts an Executor instance to theScheduler interface.)

The Schedulers class has static factory methods for creating common Scheduler implementations:

• The computationmethod returns a Scheduler for CPU-intensive work. The work is typicallyperformed by a backing thread pool⁴⁰, where the number of threads equals the number of CPUcores. If new work is scheduled but all thread in the pool are busy, then that work is enqueuedfor later execution.

• The iomethod returns a Scheduler for I/O-bound work. The work is performed by a backingthread pool that grows as needed. This growth strategy is chosen because most threads in thispool will be blocked on a read or on a write operation. These threads do not contend for theCPU.

• The immediate method returns a Scheduler that executes work immediately on the currentthread. It resembles the default execution policy explored at the beginning of this item.

Finally, if your application has a default run loop⁴¹, then consider a Scheduler implementationthat adapts it. For example, the RxAndroid⁴² project provides an AndroidSchedulers class. Its staticmethod mainThread returns a Scheduler for the main UI thread.

³⁸https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Executor.html³⁹http://reactivex.io/RxJava/javadoc/rx/schedulers/Schedulers.html⁴⁰https://en.wikipedia.org/wiki/Thread_pool⁴¹https://en.wikipedia.org/wiki/Event_loop⁴²https://github.com/ReactiveX/RxAndroid

Page 25: Michael Parker - Leanpubsamples.leanpub.com/Effectiverxjava-sample.pdfCONTENTS 1 EffectiveRxJava Thisisacollectionofitems,eachspecifyingonegeneralrule,tohelpyouwriteRxJava¹codemore

CONTENTS 22

Some methods of Observable that perform work take a Scheduler parameter specifying the threadon which the work is performed. But for the most part, you specify a Scheduler in your chain ofObservable instances using the subscribeOn and observeOn methods. Let’s consider subscribeOnfirst.

Using subscribeOn

The subscribeOn method applies to upstream Observable instances. Its Scheduler parameterspecifies the thread on which the upstream subscribe method is invoked. Consequently, if the rootObservable instance performs work, a downstream subscribeOn specifies the thread on which thework is performed. This allows us to offload I/O or CPU-intensive tasks to another thread.

Let’s redefine o1 so that it reads the integers that it emits from a file. Moreover, we will print thename of the thread of execution:

Observable<Integer> o1 = Observable.create(subscriber -> {

System.out.println("Reading file on thread " + Thread.currentThread().getNam\

e());

final List<Integer> integers = readIntegersFromFile();

for (Integer integer : integers) {

subscriber.onNext(integer);

}

subscriber.onCompleted();

});

We do not want method readIntegersFromFile to execute on the main thread, and so we pass theSchedulers.io() instance to the subscribeOn method downstream:

Observable<Integer> o2 = o1.filter(x -> (x % 2) == 1);

Observable<Integer> o3 = o2.map(x -> x * x);

Observable<Integer> o4 = o3.subscribeOn(Schedulers.io())

o4.subscribe(integer -> System.out.println("Received value: " + integer));

This revises our summary of how the observer chain is created:

• The client explicitly calls subscribe on o4.• o4 then enqueues on the Schedulers.io() instance the work of calling subscribe on o3.• A thread belonging to the thread pool backing Schedulers.io() executes this work, therebycalling subscribe on o3.

• o3 then implicitly calls subscribe on o2, which implicitly calls subscribe on o1, which thenperforms the work of calling method readIntegersFromFile and emitting each returnedvalue.

Page 26: Michael Parker - Leanpubsamples.leanpub.com/Effectiverxjava-sample.pdfCONTENTS 1 EffectiveRxJava Thisisacollectionofitems,eachspecifyingonegeneralrule,tohelpyouwriteRxJava¹codemore

CONTENTS 23

Consequently, o1 performs its work on a thread belonging to the thread pool backing the Sched-

ulers.io() instance. We validate this by running the program:

Reading file on thread RxCachedThreadScheduler-1

Received value: 1

Received value: 9

Received value: 25

RxCachedThreadScheduler-1 is the name of the thread in the thread pool backing the Sched-

ulers.io() instance. As desired, we have offloaded the work of reading the file to another thread.

But there is one consequence that is not obvious: Because a thread belonging to Schedulers.io()

is executing the work of o1, then it is that thread that invokes the downstream operators and callsour observer passed to the subscribemethod of o4. We can verify this by printing the thread nameat that point:

o4.subscribe(integer -> {

System.out.println("Received value " + integer + " on thread " + Thread.curr\

entThread().getName()));

});

When run, this prints:

Reading file on thread RxCachedThreadScheduler-1

Received value 1 on thread RxCachedThreadScheduler-1

Received value 9 on thread RxCachedThreadScheduler-1

Received value 25 on thread RxCachedThreadScheduler-1

Above the observer passed to the subscribe method of o4 simply prints the emitted values to thescreen. But we can easily imagine cases where the provided observer must execute on the mainthread. (Such as an Android application where the upstream Observable makes a network request,and the downstream observer consumes this response by updating the application state.)

To ensure that the emitted events are observed on the desired thread, we use method observeOn.

Using observeOn

The observeOn method applies to downstream Observable instances. Its Scheduler parameterspecifies the thread on which events, such as the next emitted value or the stream terminatingnormally or with an error, are observed downstream.

As we have seen, if you use subscribeOn so that the upstream Observable instance performs workon another thread, and if that work emits events that is consumed downstream, then you should

Page 27: Michael Parker - Leanpubsamples.leanpub.com/Effectiverxjava-sample.pdfCONTENTS 1 EffectiveRxJava Thisisacollectionofitems,eachspecifyingonegeneralrule,tohelpyouwriteRxJava¹codemore

CONTENTS 24

typically also use observeOn. This ensures that the consumption of the events happen on the properthread, which is typically the main thread that called subscribe.

Assuming we are writing an Android application, we can observe the events on the main thread likeso:

Observable<Integer> o4 = o3

.subscribeOn(Schedulers.io())

.observeOn(AndroidSchedulers.mainThread());

o4.subscribe(integer -> {

System.out.println("Received value " + integer + " on thread " + Thread.curr\

entThread().getName()));

});

When run, this prints:

Reading file on thread RxCachedThreadScheduler-1

Received value 1 on thread main

Received value 9 on thread main

Received value 25 on thread main

Note that by placing the observeOnmethod immediately before the call to subscribe on o4, it is theRxCachedThreadScheduler-1 thread that executes the logic of the filter and map operators. This isideal, as you should not schedule more work than necessary on your main thread.

Finally, as mentioned earlier, near every call to subscribeOn should be a call to observeOn. As DanLew⁴³ suggests in his excellent Don’t break the chain⁴⁴ article, consider defining a Transformer thatcalls both of these methods.

Understand switchMap

This has absolutely no scientific basis, but I will contend this: Understanding switchMap is anessential step on the path to using RxJava effectively. Upon understanding this operator, you willfind use cases for it everywhere. Furthermore, using it opens the door to using a wide variety ofother helpful operators.

The Javadoc for switchMap says:

Returns a new Observable by applying a function that you supply to each item emittedby the source Observable that returns an Observable, and then emitting the itemsemitted by the most recently emitted of these Observables.

⁴³http://blog.danlew.net/⁴⁴http://blog.danlew.net/2015/03/02/dont-break-the-chain/

Page 28: Michael Parker - Leanpubsamples.leanpub.com/Effectiverxjava-sample.pdfCONTENTS 1 EffectiveRxJava Thisisacollectionofitems,eachspecifyingonegeneralrule,tohelpyouwriteRxJava¹codemore

CONTENTS 25

Its marble diagram from the Javadoc is:

Marble diagram of switchMap

And its method signature is:

public final <R> Observable<R> switchMap(Func1<? super T,? extends Observable<? \

extends R>> func)

Let’s ignore the bounding by the ? super and ? extends clauses, and break this down so that it ishopefully more understandable. You have some upstream observable that is emitting values of typeT, and you have downstream subscribers that are receiving values of type R.

Upon the upstream observable emitting the first value of type T, that value is passed to the providedfunction, which returns a value of type Observable<R>. When the returned Observable<R> emits avalue of type R, downstream subscribers will receive it.

The interesting part is when the upstream observable emits the next value of type T. Again, thisvalue is passed to the provided function, which returns another value of type Observable<R>. Atthis point, downstream subscribers will stop receiving events emitted by the previously createdObservable<R>, and will start receiving events emitted by this Observable<R>. These downstreamsubscribers are unaware of this upstream change.

If you have read the item on understanding subscriber chains⁴⁵, you can intuit how switchMap

works: Whenever the function returns a new Observable<R>, the Observable<R> returned bythe switchMap operator implicitly unsubscribes from the last Observable<R> returned, and thenimplicitly subscribes to this new Observable<R> returned.

To demonstrate this power, let’s look at an example from the Khan Academy Android application⁴⁶.

⁴⁵understand-subscriber-chains.md⁴⁶https://play.google.com/store/apps/details?id=org.khanacademy.android

Page 29: Michael Parker - Leanpubsamples.leanpub.com/Effectiverxjava-sample.pdfCONTENTS 1 EffectiveRxJava Thisisacollectionofitems,eachspecifyingonegeneralrule,tohelpyouwriteRxJava¹codemore

CONTENTS 26

An example: Transcript highlighting

Khan Academy⁴⁷ has over 6,500 videos on many topics in many languages. The video player istherefore an important component of the application. The video plays above a transcript. As it plays,the corresponding text of the transcript is highlighted.

Highlighting of the transcript as the video plays

The transcript is represented as a List of TranscriptPart instances, sorted by ascending timeMillisvalues:

@AutoValue

public abstract class TranscriptPart {

/** The time at which this part of the transcript starts, in milliseconds. */

public abstract long timeMillis();

public abstract String text();

public static TranscriptPart create(long timeMillis, String text) {

checkArgument(timeMillis >= 0, "Invalid timeMillis: " + timeMillis);

return new AutoValue_TranscriptPart(timeMillis, checkNotEmpty(text));

}

}

Given a time, we can easily find the TranscriptPart that spans that time, and highlight it on screen.The real task is to create an Observable that, as the video plays, emits the playback time at regularintervals.

To start, we provide an ExoPlayer.Listener implementation to the ExoPlayer instance. ExoPlayer⁴⁸is the library from Google we use to play videos. As the ExoPlayer instance changes state, theonPlayerStateChangedmethod of its listener is invoked. This method converts each ExoPlayer stateto a state belonging to our own VideoPlayerState enumeration, and emits that enumeration valueon a subject named mPlayerStateSubject:

⁴⁷https://www.khanacademy.org/⁴⁸http://google.github.io/ExoPlayer/

Page 30: Michael Parker - Leanpubsamples.leanpub.com/Effectiverxjava-sample.pdfCONTENTS 1 EffectiveRxJava Thisisacollectionofitems,eachspecifyingonegeneralrule,tohelpyouwriteRxJava¹codemore

CONTENTS 27

private final ExoPlayer.Listener mExoPlayerListener = new ExoPlayer.Listener() {

@Override

public void onPlayerStateChanged(boolean playWhenReady, int state) {

switch (state) {

case ExoPlayer.STATE_BUFFERING:

mPlayerStateSubject.onNext(VideoPlayerState.BUFFERING);

return;

case ExoPlayer.STATE_READY:

mPlayerStateSubject.onNext(

playWhenReady ? VideoPlayerState.PLAYING : VideoPlayerSt\

ate.PAUSED

);

return;

// remaining states follow here ...

}

}

// remaining methods follow here ...

}

If the ExoPlayer instance is playing, then the case statement for STATE_READY above emits aVideoPlayerState value of PLAYING. Other case statements emit other VideoPlayerState valuesthat are consumed by subscribers of mPlayerStateSubject.

We can now transform the mPlayerStateSubject into the desired Observable that emits the videoplayback time at regular intervals as it plays:

public Observable<Long> getPlayingTimeMillisObservable() {

return mPlayerStateSubject

.map(state -> state == VideoPlayerState.PLAYING)

.distinctUntilChanged()

.switchMap(isPlaying -> {

if (isPlaying) {

return Observable.timer(

0, PLAYING_VIDEO_POLLING_RATE_MS, TimeUnit.MILLISECO\

NDS

);

} else {

return Observable.never();

}

})

// Transition from the computation scheduler of timer back to the ma\

in thread.

.observeOn(AndroidSchedulers.mainThread())

Page 31: Michael Parker - Leanpubsamples.leanpub.com/Effectiverxjava-sample.pdfCONTENTS 1 EffectiveRxJava Thisisacollectionofitems,eachspecifyingonegeneralrule,tohelpyouwriteRxJava¹codemore

CONTENTS 28

.map(ignored -> mExoPlayer.getCurrentPosition());

}

Let’s step through this from top to bottom.

The combination of the map and distinctUntilChanged operators emit to the downstream switchMap

operator a value of true every time playbacks starts, and a value of false every time playback ends.

That switchMap operator is where the magic happens:

• If the isPlaying parameter is true, meaning that the video is now playing, then the subscriberto switchMap will observe the values emitted by the Observable returned by the timer staticfactory method. The call above to timer returns an Observable that emits 0 after an initialdelay of 0milliseconds (the first parameter) and ever incrementing numbers every PLAYING_-VIDEO_POLLING_RATE_MS milliseconds (the second parameter). The static constant PLAYING_-VIDEO_POLLING_RATE_MS is defined as 300 in our application.

• If the isPlaying parameter is false, meaning that the video is no longer playing, then thesubscriber to switchMap will observe the values emitted by the Observable returned by thenever static factory method. As its name implies, that Observable never emits any values.

In sum, the subscriber of switchMap will observe the sequence of values 0, 1, 2, and so on every 300milliseconds while the video is playing, and no values while the video is not playing.

To transform this Observable to emit the current playback time every 300 milliseconds when thevideo is playing, we apply the map operator. We can ignore the input parameter entirely and simplyreturn the current playback time in milliseconds by invoking method getCurrentPosition on theExoPlayer instance. Note that the timer method emits its values on the computation Scheduler

instance, and so we must call observeOn(AndroidSchedulers.mainThread()) first to ensure thatthis happens on the main thread.

The following marble diagram illustrates the full sequence:

Page 32: Michael Parker - Leanpubsamples.leanpub.com/Effectiverxjava-sample.pdfCONTENTS 1 EffectiveRxJava Thisisacollectionofitems,eachspecifyingonegeneralrule,tohelpyouwriteRxJava¹codemore

CONTENTS 29

Marble diagram for getPlayingTimeMillisObservable

Finally, a parent UI component subscribes to the Observable returned by method getPlaying-

TimeMillisObservable. When a new time is emitted, it highlights the TranscriptPart that spansthat time.

Test emitted values using TestObserver

To test a chain of Observable instances within a component, attempt to:

• inject the root Observable instance into the component• observe the emitted values through a public or package-private Observable instance that isdownstream

You can then inject a PublishSubject into the component, thereby allowing you to control theemission of events by the root Observable. Subscribing to the public or package-private Observableinstance using a TestObserver lets you assert that it emits the expected values.

If you cannot inject the root Observable instance into the component, or if the component is toodifficult to get under test, then consider two options:

• Attempt to create a static, package-private method that encapsulates creating the chain ofObservable instances. Your component can simply delegate to this method to create the chainand subscribe to the returned Observable. Similarly, your test can invoke this method to createthe chain, and then use a TestObserver to assert the behavior of the returned Observable.

Page 33: Michael Parker - Leanpubsamples.leanpub.com/Effectiverxjava-sample.pdfCONTENTS 1 EffectiveRxJava Thisisacollectionofitems,eachspecifyingonegeneralrule,tohelpyouwriteRxJava¹codemore

CONTENTS 30

• Attempt to move the creation of the Observable instance and any related functionality intoa smaller component that allows injecting the root Observable and is easier to get under test.Again, your component can simply delegate to this smaller component, and your test candepend only on this smaller component instead of the larger one.

Below we demonstrate an example of the second strategy from the Khan Academy Androidapplication⁴⁹.

Using TestObserver

A user of the Khan Academy Android application can download videos for offline viewing. As avideo downloads, an Observable emits DownloadEvent instances that reflect the progress of thedownload. We want to observe these events so that we can display and update a notification for thedownload:

Notification updating as a video downloads

This notification contains the title of the downloading video. The title is a property of theContentItem that represents the video. However, the DownloadEvent that we receive has only aContentItemIdentifier property, which is only a unique identifier for a ContentItem instance.We can pass the ContentItemIdentifier to the fetchContentItem method of a ContentDatabase

implementation:

interface ContentDatabase {

Observable<ContentItem> fetchContentItem(ContentItemIdentifier contentItemId\

);

}

Subscribing to the returned Observablewill fetch the corresponding ContentItem from the databaseand emit it. We now define a ContentDownloadEvent class that pairs a DownloadEvent with itscorresponding ContentItem:

⁴⁹https://play.google.com/store/apps/details?id=org.khanacademy.android

Page 34: Michael Parker - Leanpubsamples.leanpub.com/Effectiverxjava-sample.pdfCONTENTS 1 EffectiveRxJava Thisisacollectionofitems,eachspecifyingonegeneralrule,tohelpyouwriteRxJava¹codemore

CONTENTS 31

@AutoValue

public abstract class ContentDownloadEvent {

public abstract DownloadEvent downloadEvent();

public abstract ContentItem contentItem();

public static ContentDownloadEvent create(final DownloadEvent downloadEvent,

final ContentItem contentItem) {

return new AutoValue_ContentDownloadEvent(downloadEvent, contentItem);

}

}

And now, by combining these elements, we can create an Observable that emits a ContentDown-

loadEvent for each emitted DownloadEvent:

public Observable<ContentDownloadEvent> getContentDownloadEventObservable() {

return mDownloadEventObservable.flatMap(downloadEvent -> {

final ContentItemIdentifier contentItemId = downloadEvent.contentItemIde\

ntifier();

return mContentDatabase.fetchContentItem(contentItemId)

.map(fetchedContentItem -> {

return ContentDownloadEvent.create(downloadEvent, fetchedCon\

tentItem)

});

});

}

Above, mDownloadEventObservable is the Observable that emits DownloadEvent instances. AndmContentDatabase is the ContentDatabase implementation.

However, the above method is inefficient: Over the course of downloading a large video, thousandsof DownloadEvent instances may be emitted with the same ContentItemIdentifier. Fetching thesame ContentItem thousands of times from the ContentDatabase will both drain the user’s batteryand cause poor performance.

To remedy this, we create a new class named ContentDownloadEventCache and move method get-

ContentDownloadEventObservable into it. It is constructedwith the same Observable<DownloadEvent>and ContentDatabase instances. But internally, it does not necessarily need to fetch from theContentDatabase the ContentItem associated with every DownloadEvent. Instead, it maintains aMap of cached ContentItem instances. This map has a maximum size and an LRU eviction policy:

Page 35: Michael Parker - Leanpubsamples.leanpub.com/Effectiverxjava-sample.pdfCONTENTS 1 EffectiveRxJava Thisisacollectionofitems,eachspecifyingonegeneralrule,tohelpyouwriteRxJava¹codemore

CONTENTS 32

public Observable<ContentDownloadEvent> getContentDownloadEventObservable() {

return mDownloadEventObservable.flatMap(downloadEvent -> {

final ContentItemIdentifier contentItemId = downloadEvent.contentItemIde\

ntifier();

final @Nullable ContentItem cachedContentItem = mCachedContentItemsMap.g\

et(contentItemId));

if (cachedContentItem != null) {

return Observable.just(ContentDownloadEvent.create(downloadEvent, ca\

chedContentItem));

} else {

return mContentDatabase.fetchContentItem(contentItemId)

.observeOn(mScheduler)

.doOnNext(fetchedContentItem -> {

mCachedContentItemsMap.put(contentItemId, fetchedContent\

Item);

})

.map(fetchedContentItem -> {

return ContentDownloadEvent.create(downloadEvent, fetche\

dContentItem)

});

}

});

}

Note that the Observable returned by method fetchContentItemmay perform its work on anotherScheduler instance like Schedulers.io(). To keep this code thread-safe, we must transition backto the main thread before adding the ContentItem to the Map in the doOnNext action. We dothis by calling observeOn(mScheduler), where mScheduler is a Scheduler that is backed bythe main thread and passed into the constructor. (In the Android application, we specify it asAndroidSchedulers.mainThread().) See the item Understand subscribeOn and observeOn⁵⁰ formore details on the use of observeOn and its related method subscribeOn.

With this in place, only the first DownloadEvent for a download will fetch the ContentItem fromthe ContentDatabase. All subsequent DownloadEvent instances will use the cached ContentItem toconstruct the corresponding ContentDownloadEvent. We now want to test this behavior.

Test setup

In the setup for our test, we create a PublishSubject onwhichwe canmanually emit DownloadEventinstances. Also, using Mockito⁵¹, we create a ContentDatabase implementation whose fetchCon-

tentItem method will return the ContentItem for a video, provided its ContentItemIdentifier isspecified as a parameter:

⁵⁰understand-subscribeon-and-observeon.md⁵¹http://mockito.org/

Page 36: Michael Parker - Leanpubsamples.leanpub.com/Effectiverxjava-sample.pdfCONTENTS 1 EffectiveRxJava Thisisacollectionofitems,eachspecifyingonegeneralrule,tohelpyouwriteRxJava¹codemore

CONTENTS 33

final ContentItem videoItem = TestUtil.randomVideoItem();

final ContentItemIdentifier videoItemId = videoItem.contentItemIdentifier();

final PublishSubject<DownloadEvent> downloadEventSubject = PublishSubject.create\

();

final ContentDatabase contentDatabase = mock(ContentDatabase.class);

when(contentDatabase.fetchContentItem(eq(videoItemId))).thenReturn(Observable.ju\

st(videoItem));

From these values we create the ContentDownloadEventCache instance. We also create a TestO-

bserver that subscribes to the Observable returned by its getContentDownloadEventObservablemethod. This will allow us to assert that we observe the expected values:

final ContentDownloadEventCache contentDownloadEventCache = new ContentDownloadE\

ventCache(

downloadEventSubject, contentDatabase, Schedulers.immediate()

);

final TestObserver<ContentDownloadEvent> testObserver = new TestObserver<>();

testObserver.subscribe(contentDownloadEventCache.getContentDownloadEventObservab\

le());

Now that our setup is complete, we begin making assertions.

Test execution

We can emit a DownloadEvent instance by passing it to the onNext method of the PublishSubject.Then, using the TestObserver that is subscribed to the Observable returned by method getCon-

tentDownloadEventObservable, we can assert that the expected ContentDownloadEvent instancesare emitted:

final DownloadEvent addedDownloadEvent = createAddedDownloadEvent(videoItemId);

final DownloadEvent receivedDataDownloadEvent = createReceivedDataDownloadEvent(\

videoItemId);

downloadEventSubject.onNext(addedDownloadEvent);

downloadEventSubject.onNext(receivedDataDownloadEvent);

final List<ContentDownloadEvent> expectedContentDownloadEvents = ImmutableList.o\

f(

ContentDownloadEvent.create(addedDownloadEvent, videoItem),

ContentDownloadEvent.create(receivedDataDownloadEvent, videoItem)

);

testObserver.assertReceivedOnNext(expectedContentDownloadEvents);

Page 37: Michael Parker - Leanpubsamples.leanpub.com/Effectiverxjava-sample.pdfCONTENTS 1 EffectiveRxJava Thisisacollectionofitems,eachspecifyingonegeneralrule,tohelpyouwriteRxJava¹codemore

CONTENTS 34

Above, method assertReceivedOnNext of TestObserver asserts that it observed the given sequenceof ContentDownloadEvent values. Because ContentDownloadEvent is defined as a value type usingAutoValue⁵², we can rely on this method to test the equality of the expected and actual sequences.

Moreover, using Mockito we can ensure that the fetchContentItem method of ContentDatabasewas invoked only once. This implies that when receivedDataDownloadEvent was emitted, theContentItem was read from the cache instead of fetched from the ContentDatabase again:

final InOrder inOrder = inOrder(contentDatabase);

inOrder.verify(contentDatabase).fetchContentItem(videoItemId);

inOrder.verifyNoMoreInteractions();

Other tests can verify the eviction of the eldest entries in the Map when it reaches capacity, and soon.

Test work scheduling using TestScheduler

Operators like debounce, delay, interval, repeat, and throttleFirst accept a Scheduler pa-rameter on which they schedule their work. If you are testing an Observable that uses one ofthese operators, have your test specify a TestScheduler instance as a parameter. This allowsyou to simulate the passing of time and execute work immediately. Do not, instead, add calls toThread.sleep in your test so that it waits for the work to execute. Such calls make your tests brittleand extremely slow to run.

Using TestScheduler

Let us return to the example of downloading a video for offline viewing⁵³. In this case, there is anObservable that emits a ByteStreamProgress instancewhenever a new sequence of bytes is receivedand written to the corresponding file. Such an instance specifies the number of bytes written so farand whether the file is now complete:

@AutoValue

public abstract class ByteStreamProgress {

public abstract long bytesWritten();

public abstract boolean isComplete();

public static ByteStreamProgress create(final long bytesWritten, final boole\

an isComplete) {

checkArgument(bytesWritten >= 0, "Invalid bytesWritten: " + bytesWritten\

);

⁵²https://github.com/mgp/effective-rxjava/blob/master/items/emit-immutable-values.md#use-autovalue⁵³test-emitted-values-using-testobserver.md

Page 38: Michael Parker - Leanpubsamples.leanpub.com/Effectiverxjava-sample.pdfCONTENTS 1 EffectiveRxJava Thisisacollectionofitems,eachspecifyingonegeneralrule,tohelpyouwriteRxJava¹codemore

CONTENTS 35

return new AutoValue_ByteStreamProgress(bytesWritten, isComplete);

}

}

Downstream, a series of map operators transform each ByteStreamProgress into a DownloadEvent

for the corresponding file. But if the user has a fast Internet connection, dozens of ByteStream-Progress instances may be generated per second. If unchecked, this will create dozens of Down-

loadEvent instances per second, which will lead to updating the corresponding notification dozensof times per second.

To remedy this, we define a static factory method named throttledObservable on the ByteStream-ProgressUtils class. On a given Observable, it uses throttleFirst to throttle the rate at whichincomplete ByteStreamProgress instances are emitted. But it does not throttle the last ByteStream-Progress specifying that the file is complete. Using this throttled Observable will throttle the rateat which DownloadEvent instances are created and observed downstream:

@VisibleForTesting

static Observable<ByteStreamProgress> throttledObservable(

final Observable<ByteStreamProgress> observable,

final long windowDuration,

final TimeUnit timeUnit,

final Scheduler scheduler) {

final Observable<ByteStreamProgress> cachedObservable =

observable.compose(ObservableUtils.cacheTransformer(1));

return Observable.merge(

cachedObservable

.filter(byteStreamProgress -> byteStreamProgress.isComplete(\

)),

cachedObservable

.filter(byteStreamProgress -> !byteStreamProgress.isComplete\

())

.throttleFirst(windowDuration, timeUnit, scheduler)

);

}

Note that the marble diagram for throttleFirst is:

Page 39: Michael Parker - Leanpubsamples.leanpub.com/Effectiverxjava-sample.pdfCONTENTS 1 EffectiveRxJava Thisisacollectionofitems,eachspecifyingonegeneralrule,tohelpyouwriteRxJava¹codemore

CONTENTS 36

Marble diagram of throttleFirst

To test throttledObservable, we create a PublishSubject that will emit ByteStreamProgressinstances without throttling, as well as a TestScheduler on which the throttling will occur. Wepass both, along with a window duration, to method throttledObservable:

final PublishSubject<ByteStreamProgress> progressSubject = PublishSubject.create\

();

final long windowDuration = 500;

final TestScheduler testScheduler = new TestScheduler();

final Observable<ByteStreamProgress> throttledObservable =

ByteStreamProgressUtils.throttledObservable(

progressSubject, windowDuration, TimeUnit.MILLISECONDS, testSche\

duler

);

After we have created the throttled Observable instance, we first subscribe to it with a TestOb-

server. We then emit a sequence of ByteSteamProgress instances on progressSubject:

// Emit incomplete progress with 10 and 20 bytes consumed in the first window.

testScheduler.advanceTimeTo(1, TimeUnit.MILLISECONDS);

progressSubject.onNext(ByteStreamProgress.create(10, false));

testScheduler.advanceTimeTo(2, TimeUnit.MILLISECONDS);

progressSubject.onNext(ByteStreamProgress.create(20, false));

// Emit incomplete progress with 30 and 40 bytes consumed in the second window.

testScheduler.advanceTimeTo(windowDuration + 1, TimeUnit.MILLISECONDS);

progressSubject.onNext(ByteStreamProgress.create(30, false));

testScheduler.advanceTimeTo(windowDuration + 2, TimeUnit.MILLISECONDS);

progressSubject.onNext(ByteStreamProgress.create(40, false));

Page 40: Michael Parker - Leanpubsamples.leanpub.com/Effectiverxjava-sample.pdfCONTENTS 1 EffectiveRxJava Thisisacollectionofitems,eachspecifyingonegeneralrule,tohelpyouwriteRxJava¹codemore

CONTENTS 37

// Emit complete progress with 50 bytes consumed in the second window.

testScheduler.advanceTimeTo(windowDuration + 3, TimeUnit.MILLISECONDS);

progressSubject.onNext(ByteStreamProgress.create(50, true));

We emit incomplete ByteStreamProgress instances with 10 and then 20 bytes in the first window.The Observable returned by throttledObservable should emit only the first instance in this pair.Similarly, we emit incomplete ByteStreamProgress instances with 30 and then 40 bytes in thesecond window. Again, throttledObservable should emit only the first instance in this pair.

Finally, we emit a complete ByteStreamProgress instance with 50 bytes in the second window. TheObservable returned by throttledObservable should not throttling it, and consequently emit it.

To assert this behavior, we create the expected sequence of ContentDownloadEvent values and passit to method assertReceivedOnNext of TestObserver:

final List<ByteStreamProgress> expectedByteStreamProgress = ImmutableList.of(

ByteStreamProgress.create(10, false),

ByteStreamProgress.create(30, false),

ByteStreamProgress.create(50, true)

);

testObserver.assertReceivedOnNext(expectedByteStreamProgress);

Note that the test above takes less than amillisecond to run onmy computer, even though it simulatesover 500 milliseconds passing. Use TestScheduler to keep your tests fast.

Use compose for operator sequences

Note that the definitive article on this subject is Don’t break the chain: use RxJava’scompose() operator⁵⁴ by Android programmer extraordinaire Dan Lew⁵⁵. You’re betterof 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’sGuava library⁵⁸. Whereas we typically represent “absent values” by using null, the Optional classencodes the optionality of such values in the type system. This helps ensure that clients deal withsuch optional values in a direct manner. For example:

⁵⁴http://blog.danlew.net/2015/03/02/dont-break-the-chain/⁵⁵http://blog.danlew.net/⁵⁶https://play.google.com/store/apps/details?id=org.khanacademy.android⁵⁷http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/base/Optional.html⁵⁸https://github.com/google/guava

Page 41: Michael Parker - Leanpubsamples.leanpub.com/Effectiverxjava-sample.pdfCONTENTS 1 EffectiveRxJava Thisisacollectionofitems,eachspecifyingonegeneralrule,tohelpyouwriteRxJava¹codemore

CONTENTS 38

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 instancecontains a value, we say that the value is present, not absent. Its isPresentmethod therefore returnstrue, 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, anddownstream subscribers want to receive only the present values of type T.

For example, a BookmarkEvent instance represents any update to the user’s bookmarks, suchas adding a bookmark, removing a bookmark, or updating the download progress associatedwith a bookmark. Every BookmarkEvent instance has an Optional value of type DownloadEvent

that is accessed through its downloadEventOptional() method. If the BookmarkEvent updates thedownload progress of a bookmark, then that Optional<DownloadEvent> contains a DownloadEventinstance with more details, such as how many bytes have been downloaded so far, what theestimated 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(downloadEve\

nt);

});

}

The filter operator and its following map operator consume an Optional<DownloadEvent>, andtogether emit only present DownloadEvent instances. Again, this is a common occurrence, but fordifferent types. And so we might define a utility method like so:

⁵⁹http://developer.android.com/guide/topics/ui/notifiers/notifications.html

Page 42: Michael Parker - Leanpubsamples.leanpub.com/Effectiverxjava-sample.pdfCONTENTS 1 EffectiveRxJava Thisisacollectionofitems,eachspecifyingonegeneralrule,tohelpyouwriteRxJava¹codemore

CONTENTS 39

/**

* @return an observable emitting the present values of {@link Optional} instanc\

es emitted by

* the observable parameter

*/

public static <T> Observable<T> observePresentValues(Observable<Optional<T>> obs\

ervable) {

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(downloadEve\

nt);

});

There are other ways to structure this, but none of them are pleasing. Reading RxJava is arguablymost straightforward when there are no temporary variables like downloadEventOptionalObserv-able, and following the left margin from top to bottom yields all operators that constitute thesubscriber chain.

To achieve these goals, we can use the compose method of Observable. This method accepts aninstance that implements Transformer<F, R>, which transforms an instance of Observable<F> toan instance of Observable<R>. The above calls to filter and then map specify a transformation fromObservable<Optional<T>> to Observable<T>. We can instead define a utility method that returnsa Transformer:

Page 43: Michael Parker - Leanpubsamples.leanpub.com/Effectiverxjava-sample.pdfCONTENTS 1 EffectiveRxJava Thisisacollectionofitems,eachspecifyingonegeneralrule,tohelpyouwriteRxJava¹codemore

CONTENTS 40

/**

* @return a transformer for emitting the present values of emitted {@link Optio\

nal} 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(downloadEve\

nt);

});

}

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 thereforetrivially immutable and safe for sharing. Moreover its sequence of filter and map operators doesnot rely on the parameterized type T. The same instance, therefore, can transform a Observ-

able<Optional<T>> for any type T.

To implement this, we create a Transformer<Optional<Object>, Object> instance that is bothprivate 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>> optionalOb\

servable) {

return optionalObservable

.filter(optional -> optional.isPresent())

.map(optional -> optional.get());

}

};

@SuppressWarnings("unchecked")

Page 44: Michael Parker - Leanpubsamples.leanpub.com/Effectiverxjava-sample.pdfCONTENTS 1 EffectiveRxJava Thisisacollectionofitems,eachspecifyingonegeneralrule,tohelpyouwriteRxJava¹codemore

CONTENTS 41

public static <T> Transformer<Optional<T>, T> presentValuesTransformer() {

return PRESENT_VALUES_TRANSFORMER;

}

The compiler cannot conclude that the cast from Transformer<Optional<Object>, Object> toTransformer<Optional<T>, T> is safe, and so it generates a compilation warning. The Suppress-Warnings("unchecked") annotation suppresses that warning.

Optimize your subscribers

Below are just a few ways to optimize your subscriptions to Observable instances.

Make the side-effect obvious

When defining a subscriber to an observable, attempt to move any filtering or transformations ofthe input to upstream operators. This makes any side-effect of the subscription more obvious.

For example, consider the following code from the video player of the Khan AcademyAndroid app⁶⁰:

getStateObservable().subscribe(state -> {

switch (state) {

case UNINITIALIZED:

case PREPARING:

case PAUSED:

case ENDED:

case ERROR:

mVideoSurfaceHolder.setKeepScreenOn(false);

return;

case BUFFERING:

case PLAYING:

mVideoSurfaceHolder.setKeepScreenOn(true);

return;

}

throw new IllegalArgumentException("Unknown player state: " + state);

});

The getStateObservable() method returns an Observable that emits the state of the video player.When that state is either the BUFFERING or PLAYING state, then we call the setKeepSreenOn methodof mVideoSurfaceHolderwith true. This keeps the screen turned on while the video is playing or is

⁶⁰https://play.google.com/store/apps/details?id=org.khanacademy.android

Page 45: Michael Parker - Leanpubsamples.leanpub.com/Effectiverxjava-sample.pdfCONTENTS 1 EffectiveRxJava Thisisacollectionofitems,eachspecifyingonegeneralrule,tohelpyouwriteRxJava¹codemore

CONTENTS 42

preparing to play. But when the video player is in any other state, then we call the setKeepScreenOnmethod with a value of false since playback is not and will not happen.

The side-effect is invoking setKeepScreenOn, but that constitutes only two lines of this 15 linemethod. Moreover, it requires close inspection to conclude that setKeepScreenOn is called for everystate. Alternatively, we can rewrite this to use the map operator:

getStateObservable()

.map(state -> {

switch (state) {

case UNINITIALIZED:

case PREPARING:

case PAUSED:

case ENDED:

case ERROR:

return false;

case BUFFERING:

case PLAYING:

return true;

}

throw new IllegalArgumentException("Unknown player state: " + state);

})

.distinctUntilChanged()

.subscribe(shouldKeepOnScreen -> {

mVideoSurfaceHolder.setKeepScreenOn(shouldKeepOnScreen);

});

The map operator returns whether the screen should be kept on for its state parameter. It is obviousthat it returns a boolean value for every state, and consequently that setKeepScreenOn can becalled for any state. And as the only line of the subscription body, the side-effect of invokingsetKeepScreenOn is also obvious. Moreover, this allows us to precede creating the subscriptionwith a call to distinctUntilChanged, which will ensure that we don’t invoke the setKeepScreenOnmethod needlessly. (In practice, this is unlikely to matter; we only mean to show how creating thinsubscribers can increase flexibility.)

Merge like subscribers

If you have multiple subscriptions to multiple Observable instances, and each of those subscriptionsconsumes the emitted values or notifications in the same manner, then use the merge static factorymethod to create a single Observable instance and subscribe to only that.

Page 46: Michael Parker - Leanpubsamples.leanpub.com/Effectiverxjava-sample.pdfCONTENTS 1 EffectiveRxJava Thisisacollectionofitems,eachspecifyingonegeneralrule,tohelpyouwriteRxJava¹codemore

CONTENTS 43

For example, in the item for understanding switchMap⁶¹, we created an Observable<Long> thatemitted the video playback time at regular intervals as a video plays. When that observable emits anew time, we scroll to and highlight the part of the transcript that spans that time:

mVideoPlayer.getPlayingTimeMillisObservable()

.subscribe(playingTime -> {

mTranscriptPlayer.highlightPartForTime(playingTime);

});

Alternatively, the user can drag the “thumb” of the seek bar and seek through the video. As the userseeks through the video, we also scroll to and highlight the part of the video that spans the time thatthe user sought:

Highlighting of the transcript as the user seeks

As the user seeks, the time sought is not emitted by the Observable from getPlayingTimeMillisOb-

servable, because the video is not playing. Instead, as the user seeks, the ExoPlayer instance passesthe sought time to the seekTo method of its registered MediaPlayerControl. We implement thismethod such that it emits on a PublishSubject<Long> named mSeekingSubject:

@Override

public void seekTo(final int timeMillis) {

mSeekingSubject.onNext(timeMillis);

}

The video player implementation exposes this subject as another Observable<Long>, availablethrough a method named getSeekingTimeMillisObservable. Again, when that observable emitsa new time, we scroll to and highlight the part of the transcript that spans that time:

mVideoPlayer.getSeekingTimeMillisObservable()

.subscribe(seekingTime -> {

mTranscriptPlayer.highlightPartForTime(seekingTime);

});

We consolidate these two subscribers using the merge method of Observable:

⁶¹understand-switch-map.md

Page 47: Michael Parker - Leanpubsamples.leanpub.com/Effectiverxjava-sample.pdfCONTENTS 1 EffectiveRxJava Thisisacollectionofitems,eachspecifyingonegeneralrule,tohelpyouwriteRxJava¹codemore

CONTENTS 44

Observable.merge(

mVideoPlayer.getPlayingTimeMillisObservable(),

mVideoPlayer.getSeekingTimeMillisObservable()

)

.subscribe(highlightTime -> {

mTranscriptPlayer.highlightPartForTime(highlightTime);

});

Now it is far easier to intuit that as the video plays or as the user seeks through the video, we alwayshighlight the part of the transcript that spans that time.