Going Reactive An architectural journey
Going Reactive An architectural journey
Matthias KäpplerOctober 2015
commit 24c61b35754ff5ca153ce37c5886279153f0d16fAuthor: Matthias Kaeppler <[email protected]>Date: Wed Mar 13 16:09:04 2013 +0100
Throw RxJava into the mix!
diff --git a/app/pom.xml b/app/pom.xmlindex 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>
Journey DownThe Stack
Layered Architecture
Featurized Architecture
Featurized Layers
The Sound Stream
Layer Objects
Rx
Screen PresenterLife-cycle dispatch,
view binding
Feature OperationsBusiness logic, data
wiring, scheduling
Storage & APINetwork, database,
flat files, syncer
Rx[Android]
Views
class SoundStreamFragment extends LightCycleSupportFragment {
@Inject @LightCycle SoundStreamPresenter presenter;
public SoundStreamFragment() { setRetainInstance(true); ... } ...}
LightCycle
A
C
BonCreate LightCycle
Dispatcher
@LightCycle
@LightCycle
@LightCycle
Presenters
class SoundStreamPresenter extends RecyclerViewPresenter<StreamItem> { ... @Override protected CollectionBinding<StreamItem> onBuildBinding(Bundle args) { return CollectionBinding.from( streamOperations.initialStreamItems()) .withAdapter(adapter) .withPager(streamOperations.pagingFunction()) .build(); }}
Paging
Paging
p1p2p3
*current*next
p1
onNext(p1)
subscribe() / next()
PublishSubject
switchOnNext
PagingFunction
Pager
Use Cases
class SoundStreamOperations {
Observable<List<StreamItem>> initialStreamItems() { return loadFirstPageOfStream() .zipWith( facebookInvites.loadWithPictures(), prependFacebookInvites()) .subscribeOn(scheduler); }
...}
Feature Data
class SoundStreamStorage {
Observable<PropertySet> streamItems(int limit) { Query query = Query.from(“SoundStreamTable”).limit(limit); return propellerRx.query(query).map(new StreamItemMapper()); }
...}
Cross-Feature Communication
Cross-Screen Messaging
updated!
Screen-to-Screen Updates
Rx Subject
Screen-to-Screen Updates
Observable<PropertySet> toggleLike(Urn urn, boolean addLike) { return storeLikeCommand.toObservable(urn, addLike) .map(toChangeSet(targetUrn, addLike)) .doOnNext(publishChangeSet);}
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!
Screen-to-Screen Updates
protected void onViewCreated(...) { eventBus.subscribe( EventQueue.ENTITY_STATE_CHANGED, new UpdateListSubscriber(adapter) );}
SoundStreamPresenter
ImplementationPatterns
Life-Cycle Subscriptions
private CompositeSubscription viewLifeCycle; protected void onViewCreated(...) { viewLifeCycle = new CompositeSubscription(); viewLifeCycle.add(...); ... } protected void onDestroyView() { viewLifeCycle.unsubscribe(); }
Fast Path & Lazy Updates
Observable<Model> maybeCached() { return Observable.concat(cachedModel(), remoteModel()).first()}
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()) }}
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!
Common Pitfalls
No-args subscribe
Observable.create(...).subscribe(/* no-args */)
OnErrorNotImplementedException
ObserveOn: onError
Observable.create((subscriber) -> { subscriber.onNext(value); subscriber.onError(new Exception());}.observeOn(mainThread()).subscribe(...)
onError cuts ahead of onNext
gets dropped!
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!
ObserveOn: Backpressure
★ Take load off of target thread˝
★ Use buffering operators (buffer, toList, …)˝
★ Use onBackpressure* operators˝
★ System.setProperty(“rx.ring-buffer.size”)
Debugging
Debugging Observables
Observable.just(1, 2, 3) .map((n) -> {return Integer.toString(n);} .observeOn(AndroidSchedulers.mainThread());
How do we debug this?
Gandalf
★ Annotation based byte code injection
★ Based on Hugo https://github.com/JakeWharton/hugo
★ AspectJ + Gradle plugin
★ @RxLogObservable, @RxLogSubscriber
Gandalf
@RxLogObservableObservable<String> createObservable() { return Observable.just(1, 2, 3) .map((n) -> {return Integer.toString(n);} .observeOn(mainThread());}
@RxLogSubscriberclass StringSubscriber extends Subscriber<String> {}
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()]
.soundcloud.com
Berlin New York San Francisco London
Stay in touch @mttkay