Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Adopting RxJava on Airbnb Android

Adopting RxJava on Airbnb Android

Presentation at Droidcon DE on June 17th, 2016

felipecsl

June 17, 2016
Tweet

More Decks by felipecsl

Other Decks in Programming

Transcript

  1. • Class based • Objects as first class citizens •

    State management • Side causes: Hidden inputs • Side effects: Hidden outputs • Hard to unit test: Mocking libraries Object Oriented Programming The status quo
  2. • Higher order functions • Pure functions • Referential transparency

    • Easy to reason about • Easy to unit test Functional Programming http://blog.jenkster.com/2015/12/what-is-functional-programming.html
  3. • Observable & Observer • Subscriber • Subscription • Producer

    • Hot & Cold Observables • Backpressure Concepts
  4. • Observable & Observer • Subscriber • Subscription • Producer

    • Hot & Cold Observables • Backpressure • Scheduler Concepts
  5. • Observable & Observer • Subscriber • Subscription • Producer

    • Hot & Cold Observables • Backpressure • Scheduler • Subject Concepts
  6. • Observable & Observer • Subscriber • Subscription • Producer

    • Hot & Cold Observables • Backpressure • Scheduler • Subject • And more! Concepts
  7. /**
 * Returns a new Observable by applying a function

    that you supply to each item emitted * by the source Observable that returns an Observable, and then emitting the items * emitted by the most recently emitted of these Observables.
 *
 * The resulting Observable completes if both the upstream Observable and the last * inner Observable, if any, complete.
 * If the upstream Observable signals an onError, the inner Observable is unsubscribed * and the error delivered in-sequence.
 * 
 * @param fund
 * a function that, when applied to an item emitted by the source Observable, * returns an Observable
 * @return an Observable that emits the items emitted by the Observable returned from * applying {@code func} to the most recently emitted item emitted by the source * Observable
 */ public final <R> Observable<R> switchMap( Func1<? super T, ? extends Observable<? extends R>> func) {
 return switchOnNext(map(func));
 }
  8. • Team Size: Getting everyone onboard and up to speed

    • Learning Curve: Steep, time consuming • Terminology: Lots of new concepts • Debugging: Unreadable stack traces Adoption Challenges
  9. java.lang.IllegalStateException: Fatal Exception thrown on Scheduler.Worker thread. at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:62) at

    android.os.Handler.handleCallback(Handler.java:739) at android.os.Handler.dispatchMessage(Handler.java:95) at android.os.Looper.loop(Looper.java:145) at android.app.ActivityThread.main(ActivityThread.java:6141) at java.lang.reflect.Method.invoke(Method.java:-2) at java.lang.reflect.Method.invoke(Method.java:372) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1399) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1194) Caused by: rx.exceptions.OnErrorFailedException: Error occurred when trying to propagate error to Observer.onError at rx.observers.SafeSubscriber._onError(SafeSubscriber.java:192) at rx.observers.SafeSubscriber.onError(SafeSubscriber.java:120) at rx.internal.operators.OperatorObserveOn$ObserveOnSubscriber.checkTerminated(OperatorObserveOn.java:254) at rx.internal.operators.OperatorObserveOn$ObserveOnSubscriber.call(OperatorObserveOn.java:186) at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:55) at android.os.Handler.handleCallback(Handler.java:739) at android.os.Handler.dispatchMessage(Handler.java:95) at android.os.Looper.loop(Looper.java:145) at android.app.ActivityThread.main(ActivityThread.java:6141) at java.lang.reflect.Method.invoke(Method.java:-2) at java.lang.reflect.Method.invoke(Method.java:372) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1399) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1194) Caused by: rx.exceptions.CompositeException: 2 exceptions occurred. at rx.observers.SafeSubscriber._onError(SafeSubscriber.java:192) at rx.observers.SafeSubscriber.onError(SafeSubscriber.java:120) at rx.internal.operators.OperatorObserveOn$ObserveOnSubscriber.checkTerminated(OperatorObserveOn.java:254) at rx.internal.operators.OperatorObserveOn$ObserveOnSubscriber.call(OperatorObserveOn.java:186) at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:55) at android.os.Handler.handleCallback(Handler.java:739) at android.os.Handler.dispatchMessage(Handler.java:95) at android.os.Looper.loop(Looper.java:145) at android.app.ActivityThread.main(ActivityThread.java:6141) at java.lang.reflect.Method.invoke(Method.java:-2) at java.lang.reflect.Method.invoke(Method.java:372) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1399) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1194) Caused by: rx.exceptions.CompositeException$CompositeExceptionCausalChain: Chain of Causes for CompositeException In Order Received => at android.util.Log.getStackTraceString(Log.java:499) at com.android.internal.os.RuntimeInit.Clog_e(RuntimeInit.java:59) at com.android.internal.os.RuntimeInit.access$200(RuntimeInit.java:43) at com.android.internal.os.RuntimeInit$UncaughtHandler.uncaughtException(RuntimeInit.java:91) at com.bugsnag.android.ExceptionHandler.uncaughtException(ExceptionHandler.java:56) at java.lang.ThreadGroup.uncaughtException(ThreadGroup.java:693) at java.lang.ThreadGroup.uncaughtException(ThreadGroup.java:690) at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:66) at android.os.Handler.handleCallback(Handler.java:739) at android.os.Handler.dispatchMessage(Handler.java:95) at android.os.Looper.loop(Looper.java:145) at android.app.ActivityThread.main(ActivityThread.java:6141) at java.lang.reflect.Method.invoke(Method.java:-2) at java.lang.reflect.Method.invoke(Method.java:372) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1399) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1194) Caused by: com.airbnb.airrequest.NetworkException at com.airbnb.airrequest.ObservableFactory.lambda$responseMapper$1(ObservableFactory.java:59) at com.airbnb.airrequest.ObservableFactory.access$lambda$1(ObservableFactory.java:-1) at com.airbnb.airrequest.ObservableFactory$$Lambda$4.call(Unknown:-1) at rx.internal.operators.OperatorMap$1.onNext(OperatorMap.java:54) at rx.internal.operators.OperatorUnsubscribeOn$1.onNext(OperatorUnsubscribeOn.java:52) at rx.internal.operators.OperatorObserveOn$ObserveOnSubscriber.call(OperatorObserveOn.java:207) at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:55) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:422) at java.util.concurrent.FutureTask.run(FutureTask.java:237) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:152) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:265) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587) at java.lang.Thread.run(Thread.java:818) Caused by: java.lang.ClassCastException: com.airbnb.android.responses.AirBatchResponse cannot be cast to com.airbnb.android.responses.ErrorResponse at com.airbnb.android.utils.NetworkUtil.error(NetworkUtil.java:357) at com.airbnb.android.utils.NetworkUtil.isExpiredOauthError(NetworkUtil.java:363) at com.airbnb.android.requests.base.ErrorLoggingAction.checkForExpiredToken(ErrorLoggingAction.java:96) at com.airbnb.android.requests.base.ErrorLoggingAction.call(ErrorLoggingAction.java:83) at com.airbnb.android.requests.base.ErrorLoggingAction.call(ErrorLoggingAction.java:33) at rx.Observable$10.onError(Observable.java:4561) at rx.internal.operators.OperatorDoOnEach$1.onError(OperatorDoOnEach.java:65) at com.airbnb.rxgroups.GroupSubscriptionTransformer$1.onError(GroupSubscriptionTransformer.java:45) at rx.Observable$30.onError(Observable.java:8280) at rx.observers.SafeSubscriber._onError(SafeSubscriber.java:157) at rx.observers.SafeSubscriber.onError(SafeSubscriber.java:120) at rx.internal.operators.OperatorDoOnEach$1.onError(OperatorDoOnEach.java:71) at rx.subjects.SubjectSubscriptionManager$SubjectObserver.onError(SubjectSubscriptionManager.java:227) at rx.internal.operators.NotificationLite.accept(NotificationLite.java:147) at rx.subjects.ReplaySubject$UnboundedReplayState.accept(ReplaySubject.java:465) at rx.subjects.ReplaySubject$UnboundedReplayState.replayObserverFromIndex(ReplaySubject.java:514) at rx.subjects.ReplaySubject$UnboundedReplayState.replayObserver(ReplaySubject.java:502) at rx.subjects.ReplaySubject.caughtUp(ReplaySubject.java:427) at rx.subjects.ReplaySubject.onError(ReplaySubject.java:387) at rx.Observable$30.onError(Observable.java:8280) at rx.observers.SafeSubscriber._onError(SafeSubscriber.java:157) at rx.observers.SafeSubscriber.onError(SafeSubscriber.java:120) at rx.internal.operators.OperatorObserveOn$ObserveOnSubscriber.checkTerminated(OperatorObserveOn.java:254) at rx.internal.operators.OperatorObserveOn$ObserveOnSubscriber.call(OperatorObserveOn.java:186) at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:55) at android.os.Handler.handleCallback(Handler.java:739) at android.os.Handler.dispatchMessage(Handler.java:95) at android.os.Looper.loop(Looper.java:145) at android.app.ActivityThread.main(ActivityThread.java:6141) at java.lang.reflect.Method.invoke(Method.java:-2) at java.lang.reflect.Method.invoke(Method.java:372) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1399) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1194) Caused by: java.lang.ClassCastException: rx.exceptions.CompositeException cannot be cast to com.airbnb.airrequest.NetworkException at com.airbnb.android.requests.AirBatchRequestObserver.onError(AirBatchRequestObserver.java:60) at rx.Observable$30.onError(Observable.java:8280) at rx.observers.SafeSubscriber._onError(SafeSubscriber.java:157) at rx.observers.SafeSubscriber.onError(SafeSubscriber.java:120) at rx.internal.operators.OperatorDoOnEach$1.onError(OperatorDoOnEach.java:68) at com.airbnb.rxgroups.GroupSubscriptionTransformer$1.onError(GroupSubscriptionTransformer.java:45) at rx.Observable$30.onError(Observable.java:8280) at rx.observers.SafeSubscriber._onError(SafeSubscriber.java:157) at rx.observers.SafeSubscriber.onError(SafeSubscriber.java:120) at rx.internal.operators.OperatorDoOnEach$1.onError(OperatorDoOnEach.java:71) at rx.subjects.SubjectSubscriptionManager$SubjectObserver.onError(SubjectSubscriptionManager.java:227) at rx.internal.operators.NotificationLite.accept(NotificationLite.java:147) at rx.subjects.ReplaySubject$UnboundedReplayState.accept(ReplaySubject.java:465) at rx.subjects.ReplaySubject$UnboundedReplayState.replayObserverFromIndex(ReplaySubject.java:514) at rx.subjects.ReplaySubject$UnboundedReplayState.replayObserver(ReplaySubject.java:502) at rx.subjects.ReplaySubject.caughtUp(ReplaySubject.java:427) at rx.subjects.ReplaySubject.onError(ReplaySubject.java:387) at rx.Observable$30.onError(Observable.java:8280) at rx.observers.SafeSubscriber._onError(SafeSubscriber.java:157) at rx.observers.SafeSubscriber.onError(SafeSubscriber.java:120) at rx.internal.operators.OperatorObserveOn$ObserveOnSubscriber.checkTerminated(OperatorObserveOn.java:254) at rx.internal.operators.OperatorObserveOn$ObserveOnSubscriber.call(OperatorObserveOn.java:186) at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:55) at android.os.Handler.handleCallback(Handler.java:739) at android.os.Handler.dispatchMessage(Handler.java:95) at android.os.Looper.loop(Looper.java:145) at android.app.ActivityThread.main(ActivityThread.java:6141) at java.lang.reflect.Method.invoke(Method.java:-2) at java.lang.reflect.Method.invoke(Method.java:372) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1399) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1194)
  10. Caused by: java.lang.ClassCastException: rx.exceptions.CompositeException cannot be cast to com.airbnb.airrequest.NetworkException at

    com.airbnb.android.requests.AirBatchRequestObserver.onError(AirBatchRequestObserver.java:60) at rx.Observable$30.onError(Observable.java:8280) at rx.observers.SafeSubscriber._onError(SafeSubscriber.java:157) at rx.observers.SafeSubscriber.onError(SafeSubscriber.java:120) at rx.internal.operators.OperatorDoOnEach$1.onError(OperatorDoOnEach.java:68) at com.airbnb.rxgroups.GroupSubscriptionTransformer$1.onError(GroupSubscriptionTransformer.java:45) at rx.Observable$30.onError(Observable.java:8280) at rx.observers.SafeSubscriber._onError(SafeSubscriber.java:157) at rx.observers.SafeSubscriber.onError(SafeSubscriber.java:120) at rx.internal.operators.OperatorDoOnEach$1.onError(OperatorDoOnEach.java:71) at rx.subjects.SubjectSubscriptionManager$SubjectObserver.onError(SubjectSubscriptionManager.java:227) at rx.internal.operators.NotificationLite.accept(NotificationLite.java:147) at rx.subjects.ReplaySubject$UnboundedReplayState.accept(ReplaySubject.java:465) at rx.subjects.ReplaySubject$UnboundedReplayState.replayObserverFromIndex(ReplaySubject.java:514) at rx.subjects.ReplaySubject$UnboundedReplayState.replayObserver(ReplaySubject.java:502) at rx.subjects.ReplaySubject.caughtUp(ReplaySubject.java:427) at rx.subjects.ReplaySubject.onError(ReplaySubject.java:387) at rx.Observable$30.onError(Observable.java:8280) at rx.observers.SafeSubscriber._onError(SafeSubscriber.java:157) at rx.observers.SafeSubscriber.onError(SafeSubscriber.java:120) at rx.internal.operators.OperatorObserveOn$ObserveOnSubscriber.checkTerminated(OperatorObserveOn.java:254) at rx.internal.operators.OperatorObserveOn$ObserveOnSubscriber.call(OperatorObserveOn.java:186) at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:55) at android.os.Handler.handleCallback(Handler.java:739) at android.os.Handler.dispatchMessage(Handler.java:95) at android.os.Looper.loop(Looper.java:145) at android.app.ActivityThread.main(ActivityThread.java:6141) at java.lang.reflect.Method.invoke(Method.java:-2) at java.lang.reflect.Method.invoke(Method.java:372) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1399) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1194)
  11. @Test public void testErrorResponseNonJSON() { server.enqueue(new MockResponse() .setBody("something bad happened")

    .setResponseCode(500)); TestRequest request = new TestRequest.Builder<String>().build(); TestSubscriber<AirResponse<String>> subscriber = new TestSubscriber<>(); observableFactory.<String>toObservable(request).subscribe(subscriber); subscriber.awaitTerminalEvent(3L, TimeUnit.SECONDS); NetworkException exception = (NetworkException) subscriber.getOnErrorEvents().get(0); assertThat(exception.errorResponse(), equalTo(null)); assertThat(exception.bodyString(), equalTo("something bad happened")); }
  12. @Test public void testUnicodeHeader() { server.enqueue(new MockResponse().setBody("\"Hello World\"")); TestRequest request

    = new TestRequest.Builder<String>() .header("Bogus", "Ӿ嶆櫮מ") .build(); observableFactory.toObservable(request) .toBlocking() .first(); RecordedRequest recordedRequest = server.takeRequest(); assertThat(recordedRequest.getHeader("Bogus"), equalTo("????")); }
  13. private final CompositeSubscription pendingSubscriptions = new CompositeSubscription(); @Override public void

    onCreate() { pendingSubscriptions.add( observable.subscribe(observer)); } @Override public void onDestroy() { pendingSubscriptions.clear(); }