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

Matthias Käppler

October 06, 2015
Tweet

More Decks by Matthias Käppler

Other Decks in Programming

Transcript

  1. Going Reactive

    An architectural journey

    View Slide

  2. View Slide

  3. Going Reactive

    An architectural journey
    Matthias Käppler

    October 2015

    View Slide

  4. commit 24c61b35754ff5ca153ce37c5886279153f0d16f
    Author: Matthias Kaeppler
    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 @@
    +
    + com.netflix.rxjava
    + rxjava-core
    + 0.5.4
    +

    View Slide

  5. Journey Down

    The Stack

    View Slide

  6. Layered Architecture

    View Slide

  7. Featurized Architecture

    View Slide

  8. Featurized Layers

    View Slide

  9. The Sound Stream

    View Slide

  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]

    View Slide

  11. Views
    class SoundStreamFragment extends LightCycleSupportFragment {
    @Inject @LightCycle SoundStreamPresenter presenter;
    public SoundStreamFragment() {
    setRetainInstance(true);
    ...
    }

    ...
    }

    View Slide

  12. LightCycle
    A
    C
    B
    onCreate
    LightCycle

    Dispatcher
    @LightCycle
    @LightCycle
    @LightCycle

    View Slide

  13. Presenters
    class SoundStreamPresenter extends RecyclerViewPresenter {
    ...

    @Override
    protected CollectionBinding onBuildBinding(Bundle args) {
    return CollectionBinding.from(

    streamOperations.initialStreamItems())
    .withAdapter(adapter)
    .withPager(streamOperations.pagingFunction())
    .build();
    }
    }

    View Slide

  14. Paging

    View Slide

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

    / next()
    PublishSubject
    switchOnNext
    PagingFunction
    Pager

    View Slide

  16. Use Cases
    class SoundStreamOperations {
    Observable> initialStreamItems() {
    return loadFirstPageOfStream()
    .zipWith(

    facebookInvites.loadWithPictures(),

    prependFacebookInvites())
    .subscribeOn(scheduler);
    }
    ...
    }

    View Slide

  17. Feature Data
    class SoundStreamStorage {
    Observable streamItems(int limit) {
    Query query = Query.from(“SoundStreamTable”).limit(limit);
    return propellerRx.query(query).map(new StreamItemMapper()); 

    }
    ...
    }

    View Slide

  18. Cross-Feature
    Communication

    View Slide

  19. Cross-Screen Messaging
    updated!

    View Slide

  20. Screen-to-Screen Updates
    Rx Subject

    View Slide

  21. Screen-to-Screen Updates
    Observable toggleLike(Urn urn, 

    boolean addLike) {
    return storeLikeCommand.toObservable(urn, addLike)
    .map(toChangeSet(targetUrn, addLike))
    .doOnNext(publishChangeSet);
    }

    View Slide

  22. Screen-to-Screen Updates
    @Override
    public void call(PropertySet changeSet) {
    eventBus.publish(

    EventQueue.ENTITY_STATE_CHANGED, 

    EntityStateChangedEvent.fromLike(changeSet)

    );
    }
    publishChangeSet: Action1
    RxSubject in disguise!

    View Slide

  23. Screen-to-Screen Updates
    protected void onViewCreated(...) {
    eventBus.subscribe(

    EventQueue.ENTITY_STATE_CHANGED,

    new UpdateListSubscriber(adapter)
    );
    }
    SoundStreamPresenter

    View Slide

  24. Implementation

    Patterns

    View Slide

  25. Life-Cycle Subscriptions
    private CompositeSubscription viewLifeCycle;


    protected void onViewCreated(...) {

    viewLifeCycle = new CompositeSubscription();
    viewLifeCycle.add(...);
    ...

    }


    protected void onDestroyView() {

    viewLifeCycle.unsubscribe();

    }

    View Slide

  26. Fast Path & Lazy Updates
    Observable maybeCached() {
    return Observable.concat(cachedModel(), remoteModel()).first()
    }

    View Slide

  27. Observable Transformers
    Observable scheduledModel() {
    return Observable.create(...).compose(schedulingStrategy)
    }
    class HighPrioUiTask extends Transformer {
    public Observable call(Observable source) {
    return source

    .subscribeOn(Schedulers.HIGH_PRIO)

    .observeOn(AndroidSchedulers.mainThread())
    }
    }

    View Slide

  28. Observable intSequence() {
    return Observable.create((subscriber) -> {
    List ints = computeListOfInts();

    for (int n : ints) {

    subscriber.onNext(n);

    subscriber.onCompleted();
    }
    }
    Deferred Execution
    Observable intSequence() {
    return Observable.defer(() -> {
    return Observable.from(computeListOfInts());
    }
    }
    expensive!

    View Slide

  29. Common Pitfalls

    View Slide

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

    View Slide

  31. ObserveOn: onError
    Observable.create((subscriber) -> {
    subscriber.onNext(value);
    subscriber.onError(new Exception());
    }.observeOn(mainThread()).subscribe(...)
    onError cuts ahead of onNext
    gets dropped!

    View Slide

  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!

    View Slide

  33. ObserveOn: Backpressure
    ★ Take load off of target thread˝
    ★ Use buffering operators (buffer, toList, …)˝
    ★ Use onBackpressure* operators˝
    ★ System.setProperty(“rx.ring-buffer.size”)

    View Slide

  34. Debugging

    View Slide

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

    View Slide

  36. Gandalf
    ★ Annotation based byte code injection

    ★ Based on Hugo 

    https://github.com/JakeWharton/hugo

    ★ AspectJ + Gradle plugin

    ★ @RxLogObservable, @RxLogSubscriber

    View Slide

  37. Gandalf
    @RxLogObservable
    Observable createObservable() {

    return Observable.just(1, 2, 3)
    .map((n) -> {return Integer.toString(n);}
    .observeOn(mainThread());
    }
    @RxLogSubscriber
    class StringSubscriber extends Subscriber {}

    View Slide

  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()]

    View Slide

  39. .
    soundcloud.com
    Berlin New York San Francisco London
    Stay in touch @mttkay

    View Slide

  40. View Slide

  41. View Slide