GTS Episode 1: Reactive programming in the wild

56
Reactive Programming in the wild Omer Iqbal Yang Bo

Transcript of GTS Episode 1: Reactive programming in the wild

Page 1: GTS Episode 1: Reactive programming in the wild

Reactive Programming in the wild

Omer Iqbal

Yang Bo

Page 2: GTS Episode 1: Reactive programming in the wild

Functional Reactive Programming

Page 3: GTS Episode 1: Reactive programming in the wild

Why?

Page 4: GTS Episode 1: Reactive programming in the wild
Page 5: GTS Episode 1: Reactive programming in the wild

Listeners/Delegates/Observers/Callbacks!

Page 6: GTS Episode 1: Reactive programming in the wild

DeathStarDelegate

DeathStar

didBlowUp:

[self fireStormTroopers]

Page 7: GTS Episode 1: Reactive programming in the wild

Problems1. Unpredictable Order2. Missing Events3. Cleaning up listeners4. Accidental Recursion5. Messy State6. Multithreading is a PAIN

Page 8: GTS Episode 1: Reactive programming in the wild
Page 9: GTS Episode 1: Reactive programming in the wild
Page 10: GTS Episode 1: Reactive programming in the wild

"our intellectual powers are rather geared to master static relations and that our powers to visualize processes evolving in time are relatively

poorly developed"

Page 11: GTS Episode 1: Reactive programming in the wild

Model events as composable values!

Page 12: GTS Episode 1: Reactive programming in the wild

Signals/Observables

next next next

completed

next next

error

Page 13: GTS Episode 1: Reactive programming in the wild

Creating a Signal- (RACSignal *)login {

return [RACSignal createSignal:^(id<RACSubscriber> subscriber){

[LoginManager loginWithSuccess:^(data){

[subscriber sendNext:data];

[subscriber sendCompleted];

} error:^(NSError *error){

[subscriber sendError:error];

}];

}];

}

Page 14: GTS Episode 1: Reactive programming in the wild

Using a signal

[[self login] subscribeNext:^(id response){

} error:^(NSError *error){

} completed:^(){

}];

Page 15: GTS Episode 1: Reactive programming in the wild

[vader login:^(id response, NSError *error) { if (error) { return; } [vader blowUpSomePlanets:^(id response, NSError *error) { if (error) { return; } [vader haveBrekkie:^(id response, NSError *error) { if (error) { return; } [vader playWithKids:^(id response, NSError *error) { if (error) { return; } }]; }]; }]; }];

Page 16: GTS Episode 1: Reactive programming in the wild

Composition! [[[[vader login] flattenMap:^RACStream *(id value) { return [vader blowUpSomePlanets]; }] flattenMap:^RACStream *(id value) { return [vader haveBrekkie]; }] flattenMap:^RACStream *(id value) { return [vader playWithKids]; }];

Page 17: GTS Episode 1: Reactive programming in the wild

flattenMap

Page 18: GTS Episode 1: Reactive programming in the wild

merge

Page 19: GTS Episode 1: Reactive programming in the wild

combineLatest

Page 20: GTS Episode 1: Reactive programming in the wild

Network RequestsRACSignal *buddies = [self requestBuddyList];RACSignal *chats = [self requestChatList];

[RACSignal combineLatest:@[buddies, chats] reduce:^id (NSArray<GxxUser *> *users, NSArray <GxxChat *> *chats){ // .. process here }];

Page 21: GTS Episode 1: Reactive programming in the wild

Composable UI FlowsGCActionViewController *vc = [GCActionViewController new];

RACSignal *camera = [[vc addButtonTitle:@"Take Photo"] flattenMap:^(){

return [self presentCameraAndTakeImage];}];

RACSignal *picker = [[vc addButtonTitle:"Choose Existing"] flattenMap:^(){

return [self presentImageSelect];}];

RACSignal *cancel = [[vc didTapCancel] flattenMap:^(){return [RACSignal error:[GCErrorUtility userCancelled]];

}];

return [RACSignal merge:@[camera, picker, cancel]];

Page 22: GTS Episode 1: Reactive programming in the wild

iOS

Page 23: GTS Episode 1: Reactive programming in the wild

ReactiveCocoa vs RxSwift

● If you’re on objective C, you don’t have a choice -> ReactiveCocoa 2.0

● RxSwift follows standard Rx conventions borrowed from RxJS, RxJava.

● ReactiveCocoa (Swift) has an awesome API with sexy operators which breaks

over Swift versions

● ReactiveCocoa (Swift) distinguishes between hot and cold signals

Page 24: GTS Episode 1: Reactive programming in the wild

Problems

Page 25: GTS Episode 1: Reactive programming in the wild

Hot Signals

● Easy to miss events● Beware of the phantom subscribe blocks● Always unsubscribe when not needed!

Page 26: GTS Episode 1: Reactive programming in the wild

[didUpdateUsers takeUntil:[self rac_willDeallocSignal]]

takeUntil is your best friend

Page 27: GTS Episode 1: Reactive programming in the wild

Cold Signals● Only perform work when subscribed to e.g Network Request, UI Flow● Beware of multiple subscribers! ● Use RACMulticast or convert to Hot Signal if multiple subscribers are needed

Page 28: GTS Episode 1: Reactive programming in the wild

AndroidYang Bo

Page 29: GTS Episode 1: Reactive programming in the wild

“modern apps are event-driven”

Page 30: GTS Episode 1: Reactive programming in the wild

System Broadcast○ Internet connectivity

○ Doze mode

○ Download progress

Lifecycle App enters:

○ Foreground

○ Background

Custom Events○ Connection established

○ User logged in

○ Data updated

○ ...

Page 31: GTS Episode 1: Reactive programming in the wild

EventBus

Page 32: GTS Episode 1: Reactive programming in the wild

Hard truths○ Throttling control

○ Grouping events

○ Higher-order events

○ Error handling and retry

○ Cache and replay

○ ...

Page 33: GTS Episode 1: Reactive programming in the wild

R X J A V AANDROID

RxJava to the rescue!

Page 34: GTS Episode 1: Reactive programming in the wild

Rx-based Framework Design

Page 35: GTS Episode 1: Reactive programming in the wild

Push PullMix

Choices of protocol

Page 36: GTS Episode 1: Reactive programming in the wild

Store NodeLatest snapshot of data

Push model

Page 37: GTS Episode 1: Reactive programming in the wild

Store NodeIncremental update

Push model

Page 38: GTS Episode 1: Reactive programming in the wild

Store NodeInvalidity

Pull model

Request data

Page 39: GTS Episode 1: Reactive programming in the wild

Characteristics of “pull” model✓ Throttling-friendly

✓ Controlled memory footprint

✘ Performance cost of requery

Page 40: GTS Episode 1: Reactive programming in the wild

Building blocks○ Network manager

public Observable<Response> send(Request request) { ...

}

○ Database manager

public <T> Observable<T> exec(Query<T> query) { ...

}

Page 41: GTS Episode 1: Reactive programming in the wild

Framework architecture

UI Data Stream Layer Local Store

ACTION UPDATE

NOTIFY

FETCH

PUSH

Page 42: GTS Episode 1: Reactive programming in the wild

Traditional approach

ContactUIData

Name: “44”Icon: “xxxx”

...

Database

User #1

User #2

User #3

User #4

Converter

Task (one-shot)

Page 43: GTS Episode 1: Reactive programming in the wild

Data Stream Layer

ContactUIData

Name: “44”Icon: “xxxx”

...

Database

User #1

User #2

User #3

User #4

Data Stream Layer

Page 44: GTS Episode 1: Reactive programming in the wild

Data Stream Layer

Database

User #1

User #2

User #3

User #4

Data Stream Layer

Trigger

Filter + Throttle

Provider

Subscriber

NOTIFY

FETCH

Page 45: GTS Episode 1: Reactive programming in the wild

Data Stream Layer

Database

User #1

User #2

User #3

User #4

Data Stream Layer

Trigger

Filter + Throttle

Provider

Subscriber

PUSH

Page 46: GTS Episode 1: Reactive programming in the wild

#1. Setup once, always up-to-date

Page 47: GTS Episode 1: Reactive programming in the wild

#2. Decouple UI from update logic

Page 48: GTS Episode 1: Reactive programming in the wild

#3. Unidirectional data flow

Page 49: GTS Episode 1: Reactive programming in the wild

Challenges○ Backpressure

Page 50: GTS Episode 1: Reactive programming in the wild

Cold observableObservable.range(1, 1000000)

.observeOn(Schedulers.io())

.doOnNext(new Action1<Object>() {

@Override

public void call(Object o) {

// slow ops

}

})

.subscribe();

Page 51: GTS Episode 1: Reactive programming in the wild

Hot observableBehaviorSubject.create(new Object())

.observeOn(Schedulers.io())

.doOnNext(new Action1<Object>() {

@Override

public void call(Object o) {

// slow ops

}

})

.subscribe();

No backpressure support!

Page 52: GTS Episode 1: Reactive programming in the wild

BehaviorSubject.create(new Object())

.onBackpressureDrop()

.observeOn(Schedulers.io())

.doOnNext(new Action1<Object>() {

@Override

public void call(Object o) {

// slow ops

}

})

.subscribe();

Page 53: GTS Episode 1: Reactive programming in the wild

Challenges○ Custom operator?

map concat merge flatMap concatMap switchMap combineLatest from

range create just first firstOrDefault last startWith zip join

switchOnNext amb retry repeat debounce sample defaultIfEmpty

skipUntil skipWhen switchCase takeUntil contains exists isEmpty

distinctUntilChanged share publish refCount replay filter skip

timeout max min reduce count collect toList toMap scan buffer

window cast cache timestamp observeOn subscribeOn delay timer

interval doOnNext doOnComplete doOnError onErrorReturn ...

DON’T BOTHER

Page 54: GTS Episode 1: Reactive programming in the wild

Challenges○ Memory leaks

Remember to unsubscribe, always

Subscription subscription = Observable.range(1, 1000000)

.subscribe();

subscription.unsubscribe();

Page 55: GTS Episode 1: Reactive programming in the wild

Summary○ Intro to Reactive Programming

○ Practical application in mobile development

○ Pitfalls & Challenges

Page 56: GTS Episode 1: Reactive programming in the wild

Thanks