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 full-size slide

  2. Going Reactive

    An architectural journey
    Matthias Käppler

    October 2015

    View full-size slide

  3. 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 full-size slide

  4. Journey Down

    The Stack

    View full-size slide

  5. Layered Architecture

    View full-size slide

  6. Featurized Architecture

    View full-size slide

  7. Featurized Layers

    View full-size slide

  8. The Sound Stream

    View full-size slide

  9. 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 full-size slide

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

    ...
    }

    View full-size slide

  11. LightCycle
    A
    C
    B
    onCreate
    LightCycle

    Dispatcher
    @LightCycle
    @LightCycle
    @LightCycle

    View full-size slide

  12. Presenters
    class SoundStreamPresenter extends RecyclerViewPresenter {
    ...

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

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

    View full-size slide

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

    / next()
    PublishSubject
    switchOnNext
    PagingFunction
    Pager

    View full-size slide

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

    facebookInvites.loadWithPictures(),

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

    View full-size slide

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

    }
    ...
    }

    View full-size slide

  16. Cross-Feature
    Communication

    View full-size slide

  17. Cross-Screen Messaging
    updated!

    View full-size slide

  18. Screen-to-Screen Updates
    Rx Subject

    View full-size slide

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

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

    View full-size slide

  20. 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 full-size slide

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

    EventQueue.ENTITY_STATE_CHANGED,

    new UpdateListSubscriber(adapter)
    );
    }
    SoundStreamPresenter

    View full-size slide

  22. Implementation

    Patterns

    View full-size slide

  23. Life-Cycle Subscriptions
    private CompositeSubscription viewLifeCycle;


    protected void onViewCreated(...) {

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

    }


    protected void onDestroyView() {

    viewLifeCycle.unsubscribe();

    }

    View full-size slide

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

    View full-size slide

  25. 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 full-size slide

  26. 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 full-size slide

  27. Common Pitfalls

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  30. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

  33. Gandalf
    ★ Annotation based byte code injection

    ★ Based on Hugo 

    https://github.com/JakeWharton/hugo

    ★ AspectJ + Gradle plugin

    ★ @RxLogObservable, @RxLogSubscriber

    View full-size slide

  34. Gandalf
    @RxLogObservable
    Observable createObservable() {

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

    View full-size slide

  35. 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 full-size slide

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

    View full-size slide