2. mdevcon 2016 takeouts
❖ Out of about 10 technical session, 3 sessions were specifically about
Rx in mobile. In yet another session Rx was mentioned a lot, too.
❖ It seems, that Rx is extremely popular on Android, and is slowly
getting there on iOS (it is sort of related to Swift 3 release date,
because Apple expects to to make language syntax backwards
compatible from Swift 3 onwards).
❖ One of the sessions took it even further: the speaker has shown that
if you build the entire stack from mobile to server in Rx (Swift,
Kotlin, Java, Javascript, C#, etc.), it will improve maintainability, as
the programming language becomes largely irrelevant, Rx reads all
the same in any programming language.
3. Motivation
(or “lifecycle’s a bitch”)
After a few hard months of development I figured that I do not understand what’s
going on in the app anymore:
❖ current state was virtually impossible to figure out in a reliable way
❖ it was practically impossible to collect all the data required for statistics
❖ some async tasks were fired and some events were propagated via the bus, but due
to that quirky phenomenon known as lifecycle some of the components simply
weren’t alive to receive events
❖ for the same lifecycle reason, callbacks were often called on already dead
components
I desperately needed a way to orchestrate all that mayhem. A way to combine results
of multiple interdependent async calls in a meaningful way and make it available to
components that did not necessarily exist yet at the time when the calls returned.
4. There were a few frameworks for chaining async calls,
but none to combine their results in time and space.
Until I came across Rx.
5. It was hard to understand.
It required to change the way of thinking.
It required to learn new building blocks instead of familiar
Android techniques and GoF patterns.
But it was definitely worth it.
…and hey, Retrofit supported it too!
6. – one colleague from a web/backend project
“Are you mad? It’s just a simple app.”
7. The ‘simple app’ happened to be
the biggest consumer of our API’s.
8. Theory: Rx and FRP
❖ Reactive (i.e. push vs pull/poll) concept is not new: Java
had java.util.Observable class since the very beginning,
Apple had KVO since at least OS X.
❖ Rx is not really FP, because even though it does draw a
lot from FP, it is mostly concerned with so-called “side
effects”, which are considered an edge-case in FP.
❖ Rx can be rather defined as reactive programming with
(asynchronous) data streams. For all practical purposes,
Rx Observable can be seen as a “push-iterator”.
9. What is an Observable
❖ The design patterns Rx is build around are Iterator and
Observable/Observer. Observable can emit so many (0..n) items by
invoking 'onNext' method on Observer, and then call 'onComplete'
or 'onError'. So basically, observables (asynchronously) emit
sequences of items that can be filtered/transformed/combined in
many ways. On so-called 'marble diagrams' emitted items are
represented by marbles (O), errors by crosses (X) and completion
by vertical dashes (|), for example: —O—O——O—X—|—>.
❖ There are also degenerate (limiting) cases of Observable: Single
and Completable. Whereas and Observable has 0..n elements,
Single has only one, and Completable has none at all.
10. Tradeoffs
(or: “don’t lose your marbles”)
The best way of understanding Rx is looking at so-called
marble diagrams.
Here’s an awesome resource, describing most building
blocks of Rx -
http://rxmarbles.com/
11. Threading, Hot & Cold
❖ Observable can be subscribed and observed on different threads.
Observable will run its code on subscribe-thread, and call Observer
on observe-thread. By default, everything runs on the current
thread. For Android, a special main thread is available.
❖ There are cold and hot Observables: cold ones start emitting items
only when subscribed to, hot ones are independent; a special case
of Observable is ConnectableObservable, it starts emitting items
only when 'connect()' method is called on it, which allows to
subscribe more than one subscriber and be sure that they all receive
the same items. a regular Observable can be made
ConnectableObservable by invoking '.publish()' method on it.
12. Sharing cold observables
❖ Hot observables (such as user input) produce events on
their own, making multiple subscriptions to them poses
no problem.
❖ Cold observables, which actually start executing their
code only upon subscription, need to be told to wait until
all subscribers have subscribed. This can be achieved by
converting them to ConnectableObservable’s:
final ConnectableObservable<Integer> connectableObservable = observable.publish();
shared.subscribe(subscriber1);
shared.subscribe(subscriber2);
shared.connect();
13. Subject are both Observables and Subscribers
❖A combination of Observable and Observer is called Subject.
❖Different subject can be used for their special behaviors, for example
BehaviorSubject always keeps its last item cached for new subscribers and
is therefore very useful for all sorts of UI subscribers that need the latest
state.
❖ PublishSubject and BehaviorSubject proved to be the most useful in my
experience.
❖ Though I also found some use for ReplaySubject too:
private final ReplaySubject<Timestamped<Request>> requestReplaySubject =
ReplaySubject.createWithTimeAndSize(MAX_CACHED_REQUEST_AGE_IN_SECONDS, TimeUnit.SECONDS, MAX_REQUEST_CACHE_SIZE,
Schedulers.immediate());
14. How to subscribe
❖ It is possible to subscribe to Observable with Action1,
Observer or Subscriber; with Action1, you must handle
onNext/onComplete/onError in separate actions;
Observer can (and should) handle all three; Subscriber is
the same as Observer, but it keeps reference to its own
subscription and therefore can unsubscribe whenever it is
needed.
❖ .materialize() wraps all events (i.e. onNext/onError/
onCompleted) into Notification and only emits
onNext(Notification<>), so Action1 will suffice.
15. How to create an Observable
❖ It is highly unlikely that you will ever need to create an Observable manually -
❖ first of all, because they have a strict contract which is rather hard to implement properly,
❖ but most importantly because there are plenty of simpler ways of making one, such as
static factory methods of Observable class:
❖ .just(), .from(), .range(), .empty(), .error(), .fromCallable(), …
❖ To create an Observable from a value (or several values), use 'just()'; to create if from a
collection or array, use 'from()'; NB: if you use 'just()' on collection/array, your Observable
will emit only 1 item, this collection/array as a whole; if you use 'from()' on collection/
array, your Observable will emit contents of collection/array as separate items.
❖ Also, you can concatenate Observables with .concatWith()
❖ Also, since Subjects are also in part Observables, every concrete Subject class has a static
factory method .create()
18. …and
You will also need
RxAndroid lib
…for 1 method only:
AndroidSchedulers.mainThr
ead()
19. The purpose
❖ Primary practical purpose of Rx on Android for me: it
solves so-called “callback hell" (with nested callbacks).
❖ Rx allows to combine results, but it can also split and
then combine again, i.e. you can derive several
Observables from one, append different processing to
them, combine their results and execute as a whole only
once (useful for some cases of data enrichment).
❖ Reactive means paradigm shift from pull/poll to push,
which is a major relief on a mobile platform.
20. What is easy
❖ easy to solve threading issues: .subscribeOn(), .observeOn()
❖ easy to solve “too many events” issues: .sample(), .throttle(), .debounce()
❖ easy to combine multiple asynchronous data streams: .zip(), .combineLatest()
❖ easy to split one data stream, process it in different ways, and possibly combine
again afterwards (useful in some data enrichment scenarios)
❖ easy to implement retry/fallback scenarios: .onErrorResume(),
onErrorNext(), .retry([so many times]), etc.
❖ easy to enforce timeout: .timeout()
❖ easy to timestamp events or measure time intervals between
them: .timestamp(), .interval()
21. Too many events: .sample(), .throttleFirst(), .debounce()
❖ This situation often happens in user input processing,
such as drag, scroll, etc.
❖ .throttle…() and .debounce() handle this situation
automatically
❖ .sample() allows to control it manually:
final PublishSubject<Void> sampler = PublishSubject.create();
final Subscription subscription = events.sample(sampler).subscribe(new Action1<Integer>() { // …
22. Feel the power
❖ Obviously, you would expect to find the basic FP toolset
of .filter(), .map(), .reduce(), .flatMap(), and such. But Rx does not stop there.
❖ .scan()
final List<String> simple = Observable.<String>just("a", "b", "c", "d", "e", "f").scan("_", new Func2<String, String, String>() {
@Override
public String call(String lhs, String rhs) {
return lhs + rhs;
}
}).toList().toBlocking().single(); // [_, _a, _ab, _abc, _abcd, _abcde, _abcdef]
❖ .collect():
final String result = Observable.<Integer>range(1, 5).collect(new Func0<StringBuilder>() {
@Override
public StringBuilder call() {
return new StringBuilder();
}
}, new Action2<StringBuilder, Integer>() {
@Override
public void call(StringBuilder stringBuilder, Integer integer) {
stringBuilder.append(String.format("%s, ", integer));
}
}).map(new Func1<StringBuilder, String>() {
@Override
public String call(StringBuilder stringBuilder) {
return stringBuilder.toString();
}
}).toBlocking().single(); // 1, 2, 3, 4, 5,
23. .zip() and .combineLatest()
❖ There are plenty of ways of combining data streams
(items emitted by Observables); for instance
'combineLatest()' method emits a combined item
whenever any of the source Observables emits an item
(last values of other sources are taken from cache); 'zip()'
method emits a combined item whenever all source
Observables emitted a new item, therefore the name (it
works like a zipper); to assign indexes to items emitted by
an Observable, one can 'zip' it with an infinite Iterable
producing integer numbers (by using 'zipWith()' method).
24. How to number events
public class RxIndex {
private static class InfiniteIterator implements Iterator<Integer> {
private int index = 0;
@Override
public boolean hasNext() {
return true;
}
@Override
public Integer next() {
return index++;
}
@Override
public void remove() {
}
}
private static class InfiniteIterable implements Iterable<Integer> {
private final Iterator<Integer> iterator = new InfiniteIterator();
@Override
public Iterator<Integer> iterator() {
return iterator;
}
}
public static <T> Observable<Indexed<T>> zipWithIndex(final Observable<T> values) {
return values.zipWith(new InfiniteIterable(), new Func2<T, Integer, Indexed<T>>() {
@Override
public Indexed<T> call(T value, Integer index) {
return new Indexed<T>(value, index);
}
});
}
}
25. .merge(), .map(), .flatMap() and .concatMap()
❖ If you need to merge items emitted by multiple
Observables in the order of appearance, use 'merge()'
method.
❖ If you have an Observable emitting collections of items,
and you want to combine all those collections into one,
use 'flatMap()'; if it is done asynchronously and you
need to ensure the order of items, use 'concatMap()'
instead.
26. .defer()
❖ Obviously, once an Observable did emit all its items, it
becomes empty, so subscribing again will not give any
result; if we need that it does its work again every time,
we can use factory method 'defer()', it makes sure that it
is always a new instance of Observable for every
subscription (this is useful for service calls and such).
27. .toList() and .take(1)
❖ It is more convenient to return series of values as an
Observable from value type; The benefit of doing it so is
that you can simply add '.toList()' to receive all values at
once or '.take(1)' to receive only first value (if available).
28. Typical Rx error messages
❖ Make sure errors are handled either in Subscribers or in
onError() methods, otherwise you will
get OnErrorNotImplementedException.
❖ Always specify backpressure policy, such as
‘onBackpressureDrop()', when you expect many events
which might not be processed instantly, otherwise you
will get a MissingBackpressureException.
29. Dealing with errors
❖ First of all, you can always add thread and stack trace logging
in .doOnNext(), or anywhere for that matter.
❖ Secondly, you can use a special overload of .flatMap() which takes 3
parameters for onNext/onError/onCompleted respectively to propagate
errors and completion of the top-level observable to the result:
❖ Thirdly, you can use .materialize() to produce Notification wrapper for any
kind of event (i.e. onNext/onError/onCompleted). This can be particular
useful to store last status of some server request (such as an authentication
attempt).
public final <R> Observable<R> flatMap(
Func1<? super T, ? extends Observable<? extends R>> onNext,
Func1<? super Throwable, ? extends Observable<? extends R>> onError,
Func0<? extends Observable<? extends R>> onCompleted) {
return merge(mapNotification(onNext, onError, onCompleted));
30. private static void initDebugLoggingForSwallowedExceptions() {
final RxJavaPlugins rxJavaPlugins = RxJavaPlugins.getInstance();
rxJavaPlugins.registerErrorHandler(new RxErrorHandler());
rxJavaPlugins.registerObservableExecutionHook(new RxObservableExecutionHook());
}
Error- and Execution hooks
❖ Finding the source of errors and following actual flow of
Rx processing can prove difficult. There is a remedy
though:
public class RxErrorHandler extends RxJavaErrorHandler {
@Override
public void handleError(Throwable t) {
final Thread thread = Thread.currentThread();
final String threadInfo = String.format("[thread: #%d '%s']", thread.getId(), thread.getName());
Log.d(threadInfo);
Log.d(t);
}
31. Execution hook contd.
public class RxObservableExecutionHook extends RxJavaObservableExecutionHook {
@Override
public <T> Observable.OnSubscribe<T> onCreate(Observable.OnSubscribe<T> f) {
@Override
public <T> Observable.OnSubscribe<T> onSubscribeStart(Observable<? extends T> observableInstance,
Observable.OnSubscribe<T> onSubscribe) {
@Override
public <T> Subscription onSubscribeReturn(Subscription subscription) {
@Override
public <T, R> Observable.Operator<? extends R, ? super T> onLift(Observable.Operator<? extends R, ? super T> lift) {
@Override
public <T> java.lang.Throwable onSubscribeError(java.lang.Throwable e) {
Log.d(logEntryWithStack(String.format("%s", e.getMessage())));
return super.onSubscribeError(e);
}
public static String logEntryWithStack(final String firstLine) {
final Thread thread = Thread.currentThread();
final String threadInfo = String.format("[thread: #%d '%s']", thread.getId(), thread.getName());
final Observable<String> lines = Observable.from(thread.getStackTrace()).filter(new Func1<StackTraceElement,
Boolean>() {
@Override
public Boolean call(StackTraceElement stackTraceElement) {
final String className = stackTraceElement.getClassName();
return (className.startsWith(PACKAGE) && !className.contains("_") && !
className.endsWith(RxObservableExecutionHook.class.getName()));
}
}).map(new Func1<StackTraceElement, String>() {
@Override
public String call(StackTraceElement stackTraceElement) {
return String.format("%s.%s() %s:%d", stackTraceElement.getClassName(),
stackTraceElement.getMethodName(), stackTraceElement.getFileName(), stackTraceElement.getLineNumber());
}
}).startWith(firstLine, threadInfo);
32. Thread safety and memory concerns
❖ According to the contract, Observable code must produce all events on
the same thread. This rule is easily broken by calling Subjects’ .onNext()
method from different threads. How to cope? Use . serialized() which
ensures that contract is met:
❖ Be careful with operators such as .distinct(),
prefer .distinctUntilChanged()
❖ Prevent memory leaks by keeping connections in
CombinedSubscriptions or use Subscribers (which can unsubscribe
themselves):
subject.asObservable().serialized();
final CompositeSubscription compositeSubscription = new CompositeSubscription();
compositeSubscription.add(subscription);
33. Testing, and other special cases
❖ For testing, you have TestScheduler to manually control virtual time and
TestSubscriber with all kinds of asserts defined in it.
❖ When synchronicity and reactiveness is not what you are testing,
use .toBlocking()
❖ .toBlocking() kills all reactiveness and converts an Observable to a plain
value, so it kind of defeats the whole idea of Rx. However, besides tests, it
might prove useful in all sorts of handlers and interceptors that are
expected to block further processing on their thread.
❖ .materialize() can be used to process any outcome (i.e. also error or premature
completion) as valid outcome. Besides tests, it can come in handy when you
want to keep the specific error for future reference, such as in login scenario.
34. Gains
(or “combine and rule”)
❖ easy to combine results of several async calls in time and
space
❖ easy to maintain and propagate state
❖ easy to skip events or wait for a certain condition to occur
❖ easy to process input forms
❖ easy to process click/scroll events
❖ lifecycle is not an issue anymore
35. More to explore
❖ A brand new O’Reilly book “Reactive programming with RxJava” by Tomasz
Nurkiewicz & Ben Christensen
❖ Rx JavaDoc: http://reactivex.io/RxJava/javadoc/overview-summary.html
❖ Rx marble diagrams: http://rxmarbles.com/
❖ “The introduction to Reactive Programming you've been missing by André
Staltz”: https://gist.github.com/staltz/868e7e9bc2a7b8c1f754
❖ an awesome GOTO presentation “Going Reactive, An Android Architectural
Journey by Matthias Käppler” - https://www.youtube.com/watch?
v=R16OHcZJTno
❖ My own playground at GitHub - https://github.com/maxvol/
RxJavaPlayground (feel free to mess it up)