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

Going Reactive - An Architectural Journey

Going Reactive - An Architectural Journey

Initially held at GOTO Copenhagen 2015
http://gotocon.com/cph-2015/presentation/Going%20Reactive%20-%20An%20Android%20architectural%20journey

Ever since its inception in 2008, the Android platform has remained challenging to work with when building large, event driven applications in a consistent and reliable way. We've been among the earliest companies to embrace FRP as a vehicle to tackle some of the complexity arising as a direct result of that. In this talk I will give an overview of what our Android application architecture has evolved into, and how the use of RxJava for dealing with asynchronous streams of events has impacted its design, from data sources, to business logic and presentation

5cfa38a7e54e6c6a850dc6169a699246?s=128

Matthias Käppler

October 06, 2015
Tweet

Transcript

  1. Going Reactive
 An architectural journey

  2. None
  3. Going Reactive
 An architectural journey Matthias Käppler
 October 2015

  4. commit 24c61b35754ff5ca153ce37c5886279153f0d16f Author: Matthias Kaeppler <mk@soundcloud.com> Date: Wed Mar 13

    16:09:04 2013 +0100 Throw RxJava into the mix! diff --git a/app/pom.xml b/app/pom.xml index 86ba988..1bf5109 100644 --- a/app/pom.xml +++ b/app/pom.xml @@ -178,6 +178,11 @@ + <dependency> + <groupId>com.netflix.rxjava</groupId> + <artifactId>rxjava-core</artifactId> + <version>0.5.4</version> + </dependency> </dependencies>
  5. Journey Down
 The Stack

  6. Layered Architecture

  7. Featurized Architecture

  8. Featurized Layers

  9. The Sound Stream

  10. Layer Objects Rx Screen Presenter
 Life-cycle dispatch,
 view binding Feature

    Operations
 Business logic, data 
 wiring, scheduling Storage & API
 Network, database,
 flat files, syncer Rx[Android]
  11. Views class SoundStreamFragment extends LightCycleSupportFragment { @Inject @LightCycle SoundStreamPresenter presenter;

    public SoundStreamFragment() { setRetainInstance(true); ... }
 ... }
  12. LightCycle A C B onCreate LightCycle
 Dispatcher @LightCycle @LightCycle @LightCycle

  13. Presenters class SoundStreamPresenter extends RecyclerViewPresenter<StreamItem> { ...
 @Override protected CollectionBinding<StreamItem>

    onBuildBinding(Bundle args) { return CollectionBinding.from(
 streamOperations.initialStreamItems()) .withAdapter(adapter) .withPager(streamOperations.pagingFunction()) .build(); } }
  14. Paging

  15. Paging p1 p2 p3 *current *next p1 onNext(p1) subscribe()
 /

    next() PublishSubject switchOnNext PagingFunction Pager
  16. Use Cases class SoundStreamOperations { Observable<List<StreamItem>> initialStreamItems() { return loadFirstPageOfStream()

    .zipWith(
 facebookInvites.loadWithPictures(),
 prependFacebookInvites()) .subscribeOn(scheduler); } ... }
  17. Feature Data class SoundStreamStorage { Observable<PropertySet> streamItems(int limit) { Query

    query = Query.from(“SoundStreamTable”).limit(limit); return propellerRx.query(query).map(new StreamItemMapper()); 
 } ... }
  18. Cross-Feature Communication

  19. Cross-Screen Messaging updated!

  20. Screen-to-Screen Updates Rx Subject

  21. Screen-to-Screen Updates Observable<PropertySet> toggleLike(Urn urn, 
 boolean addLike) { return

    storeLikeCommand.toObservable(urn, addLike) .map(toChangeSet(targetUrn, addLike)) .doOnNext(publishChangeSet); }
  22. Screen-to-Screen Updates @Override public void call(PropertySet changeSet) { eventBus.publish(
 EventQueue.ENTITY_STATE_CHANGED,

    
 EntityStateChangedEvent.fromLike(changeSet)
 ); } publishChangeSet: Action1<PropertySet> RxSubject in disguise!
  23. Screen-to-Screen Updates protected void onViewCreated(...) { eventBus.subscribe(
 EventQueue.ENTITY_STATE_CHANGED,
 new UpdateListSubscriber(adapter)

    ); } SoundStreamPresenter
  24. Implementation
 Patterns

  25. Life-Cycle Subscriptions private CompositeSubscription viewLifeCycle;
 
 protected void onViewCreated(...) {


    viewLifeCycle = new CompositeSubscription(); viewLifeCycle.add(...); ...
 }
 
 protected void onDestroyView() {
 viewLifeCycle.unsubscribe();
 }
  26. Fast Path & Lazy Updates Observable<Model> maybeCached() { return Observable.concat(cachedModel(),

    remoteModel()).first() }
  27. Observable Transformers Observable<Model> scheduledModel() { return Observable.create(...).compose(schedulingStrategy) } class HighPrioUiTask<T>

    extends Transformer<T, T> { public Observable<T> call(Observable<T> source) { return source
 .subscribeOn(Schedulers.HIGH_PRIO)
 .observeOn(AndroidSchedulers.mainThread()) } }
  28. Observable<Integer> intSequence() { return Observable.create((subscriber) -> { List<Integer> ints =

    computeListOfInts();
 for (int n : ints) {
 subscriber.onNext(n);
 subscriber.onCompleted(); } } Deferred Execution Observable<Integer> intSequence() { return Observable.defer(() -> { return Observable.from(computeListOfInts()); } } expensive!
  29. Common Pitfalls

  30. No-args subscribe Observable.create(...).subscribe(/* no-args */) OnErrorNotImplementedException

  31. ObserveOn: onError Observable.create((subscriber) -> { subscriber.onNext(value); subscriber.onError(new Exception()); }.observeOn(mainThread()).subscribe(...) onError

    cuts ahead of onNext gets dropped!
  32. ObserveOn: Backpressure public void onStart() { request(RxRingBuffer.SIZE); } public void

    onNext(final T t) { ... if (!queue.offer(on.next(t))) { onError(new MissingBackpressureException()); return; } schedule(); } 16!
  33. ObserveOn: Backpressure ★ Take load off of target thread˝ ★

    Use buffering operators (buffer, toList, …)˝ ★ Use onBackpressure* operators˝ ★ System.setProperty(“rx.ring-buffer.size”)
  34. Debugging

  35. Debugging Observables Observable.just(1, 2, 3) .map((n) -> {return Integer.toString(n);} .observeOn(AndroidSchedulers.mainThread());

    How do we debug this?
  36. Gandalf ★ Annotation based byte code injection
 ★ Based on

    Hugo 
 https://github.com/JakeWharton/hugo
 ★ AspectJ + Gradle plugin
 ★ @RxLogObservable, @RxLogSubscriber
  37. Gandalf @RxLogObservable Observable<String> createObservable() {
 return Observable.just(1, 2, 3) .map((n)

    -> {return Integer.toString(n);} .observeOn(mainThread()); } @RxLogSubscriber class StringSubscriber extends Subscriber<String> {}
  38. Gandalf [@Observable :: @InClass -> MainActivity :: @Method 
 ->

    createObservable()] [@Observable#createObservable -> onSubscribe() :: 
 @SubscribeOn -> main] [@Observable#createObservable -> onNext() -> 1] [@Observable#createObservable -> onNext() -> 2] [@Observable#createObservable -> onNext() -> 3] [@Observable#createObservable -> onCompleted()] [@Observable#createObservable -> onTerminate() :: 
 @Emitted -> 3 elements :: @Time -> 4 ms] [@Observable#createObservable -> onUnsubscribe()]
  39. . soundcloud.com Berlin New York San Francisco London Stay in

    touch @mttkay
  40. None
  41. None