RxJava и Android. Плюсы, минусы, подводные камни

Preview:

Citation preview

, Tips and, Tips andTricksTricks

RxJavaRxJava

Yaroslav Heriatovych

RxJavaRxJavaBackgroundBackground

ObservableObservable

ObservableObservable

Observables fill the gap by being the ideal implementation of access toasynchronous sequences of multiple items

single items multiple items

synchronous T getData() Iterable<T> getData()

asynchronous Future<T> getData() Observable<T> getData()

PrimitivesPrimitives public interface Observer <T> { void onCompleted(); void onError(java.lang.Throwable throwable); void onNext(T t); }

public class Observable <T> { public final static <T> Observable<T> create(OnSubscribe<T> f) public rx.Subscription subscribe(rx.Observer<? super T> observer) // ... }

public static interface OnSubscribe<T> extends Action1<Subscriber<? super T>> {}

public interface Subscription { public void unsubscribe(); public boolean isUnsubscribed();}

public abstract class Subscriber<T> implements Observer<T>, Subscription {...}

Create ObservableCreate Observable

Observable<String> o = Observable.from("a", "b", "c");Observable<String> o = Observable.just("one object");

Observable<String> o = Observable.create(new Observable.OnSubscribe<String>() { @Override public void call(Subscriber<? super String> subscriber) { subscriber.onNext("Hello"); subscriber.onNext("World"); subscriber.onCompleted(); } });

Transform ObservableTransform Observable

o.skip(10).take(5) .map(new Func1<String, String>() { @Override public String call(String s) { return s.toUpperCase(); } }) .subscribe(new Action1<String>() { @Override public void call(String s) { Log.d("rx", s) } });

SchedulersSchedulers

o.skip(10).take(5) .observeOn(Schedulers.computation()) .map(new Func1<String, String>() { @Override public String call(String s) { return s.toUpperCase(); } }) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Action1<String>() { @Override public void call(String s) { Log.d("rx", s) } });

RxJava: Library, RxJava: Library, not Framework

LambdaLambdathe new old thingthe new old thing

Used lambdas before it was coolUsed lambdas before it was cool

Observable<Integer> xs = Observable.just(1, 2, 3, 4, 5, 6, 7); xs.filter(new Func1<Integer, Boolean>() { @Override public Boolean call(Integer x) { return x % 2 == 0; } }).map(new Func1<Integer, Integer>() { @Override public Integer call(Integer x) { return x + 10; } }).subscribe(new Action1<Integer>() { @Override public void call(Integer x) { Rx.this.print(x); } });

Anonymous classes hide logic behind thenoise

More code - more bugs

MapMap

FilterFilter

Observable<Integer> xs = Observable.just(1, 2, 3, 4, 5, 6, 7); xs.filter(x -> x % 2 == 0) .map(x -> x + 10) .subscribe(x -> print(x));

Less noiseClear logicConcise

RetrolambdaRetrolambdagradle-retrolambdagradle-retrolambda

Once more:

Java8 for android developmentNative support in Android StudioMagic

One more thingOne more thingIt's safer

static class MyActivity extends Activity { Handler handler = new Handler(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); String str = "uamobile"; handler.postDelayed(new Runnable() { @Override public void run() { Log.d("Rx", str); } }, 10000); } }

class MyActivity$1 extends java.lang.Object implements java.lang.Runnable{final java.lang.String val$str;

final MyActivity this$0;

MyActivity$1(cMyActivity, java.lang.String); Code: 0: aload_0 1: aload_1 2: putfield 5: aload_0 6: aload_2 7: putfield 10: aload_0 11: invokespecial 14: return

public void run(); Code: 0: ldc 2: aload_0 3: getfield 6: invokestatic 9: pop 10: return}

One more thingOne more thingIt's safer

static class MyActivity extends Activity { Handler handler = new Handler(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); String str = "uamobile"; handler.postDelayed(() -> Log.d("Rx", str), 10000); } }

final class MyActivity$$Lambda$1 extends java.lang.Object implements java.lang.Runnable{public void run(); Code: 0: aload_0 1: getfield 4: invokestatic 7: return

public static java.lang.Runnable lambdaFactory$(java.lang.String); Code: 0: new 3: dup 4: aload_0 5: invokespecial 8: areturn}

One abstractionOne abstractionto rule them allto rule them all

final View.OnClickListener clickListener = new View.OnClickListener() { @Override public void onClick(View view) { //... } };

new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { //... } };

com.squareup.okhttp.Callback okCallback = new com.squareup.okhttp.Callback() { @Override public void onFailure(Request request, IOException e) { //... } @Override public void onResponse(Response response) throws IOException { //... } };

Observable<Void> click

Observable<Intent> broadcasts

Observable<Response> responses

ViewObservable.text(editText) //Observable<OnTextChangeEvent> .map(event -> event.text) //Observable<CharSequence> .filter(cs -> !TextUtils.isEmpty(cs)) //Observable<CharSequence> .map(cs -> cs.toString().toUpperCase()) //Observable<String> .take(5) //Observable<String> .subscribe(str -> handleString(str)); //Subscription

editText.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {} @Override public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {} int count = 5; @Override public void afterTextChanged(Editable editable) { if (!TextUtils.isEmpty(editable)) { String transformed = editable.toString().toUpperCase(); handleString(transformed); count--; if (count == 0) { editText.removeTextChangedListener(this); } } } });

Events as a streamEvents as a stream

take(n)take(n)

Abstract over event producerAbstract over event producer

Observable<CharSequence> input = ViewObservable.text(editText) .map(event -> event.text);

input.filter(cs -> !TextUtils.isEmpty(cs)) .map(cs -> cs.toString().toUpperCase()) .take(5) .subscribe(str -> handleString(str));

Observable<CharSequence> input = AndroidObservable.fromBroadcast(ctx, intentFilter) .map(intent -> intent.getStringExtra("magic_str"));

Call call = okHttpClient.newCall(...); Observable<String> input = executeOkCall(call) .map(response -> { try { return response.body().string(); } catch (IOException e) { throw new RuntimeException(e); } }) .flatMap(bodyString -> Observable.from(bodyString.split("\n"))) .observeOn(AndroidSchedulers.mainThread());

flatMapflatMap

public Observable<String> fromOkCall1(Call call){ return Observable.create(subscriber -> { try { Response response = call.execute(); subscriber.onNext(response.body().string()); subscriber.onCompleted(); } catch (IOException e) { subscriber.onError(e); } }); }

public Observable<String> fromOkCall2(Call call){ return Observable.create(subscriber -> { call.enqueue(new Callback() { @Override public void onFailure(Request request, IOException e) { subscriber.onError(e); } @Override public void onResponse(Response response) throws IOException { try { subscriber.onNext(response.body().string()); subscriber.onCompleted(); } catch (IOException e) { subscriber.onError(e); } } }); }); }

LazynessLazyness

Observable<String> observable = Observable.create(new Observable.OnSubscribe<String>() { @Override public void call(Subscriber<? super String> subscriber) { subscriber.onNext("Hello"); subscriber.onNext("uamobile"); subscriber.onCompleted(); } });

Simple (cold) ObservableSimple (cold) Observable

observable.subscribe(new Action1<String>() { @Override public void call(String s) { handleString(s); } });

Observable<String> lastNcRead = mBriefcaseHelper.getBriefcaseObservable() .flatMap(list -> { Observable<Briefcase> briefcaseObservable = Observable.from(list); Observable<String> maxRead = briefcaseObservable .ofType(NotificationReadBriefcase.class) .map(b -> b.attrs.version) .firstOrDefault("0"); Observable<String> maxClear = briefcaseObservable .ofType(NotificationClearBriefcase.class) .map(b -> b.attrs.version) .firstOrDefault("0"); return Observable.zip( maxRead, maxClear, (a, b) -> a.compareTo(b) > 0 ? a : b ); }); Observable<Boolean> hasNotificationsObservable = mDatastore.getNotifications() .switchMap(notifications -> lastNcRead.flatMap(lastNC -> Observable.from(notifications) .takeWhile(n -> n.getId().compareTo(lastNC) > 0) .isEmpty().map(empty -> !empty) ) ).observeOn(AndroidSchedulers.mainThread());

hasNotificationsObservable .subscribe(hasNotifications -> view.setEnabled(hasNotifications));

Do nothing

Execute allabove

Cold and lazyCold and lazy

Observable<Integer> cold = Observable.create(subscriber -> { int result = new Random().nextInt(50); subscriber.onNext(result); subscriber.onCompleted(); }); cold.subscribe(n -> print(n)); //13 cold.subscribe(n -> print(n)); //42 =(

Hot and sharedHot and shared

ConnectableObservable<Integer> cold = Observable.<Integer>create(subscriber -> { int result = new Random().nextInt(50); subscriber.onNext(result); subscriber.onCompleted(); }).publish(); cold.subscribe(n -> print(n)); //40 cold.subscribe(n -> print(n)); //40 cold.connect();

publish & connectpublish & connect

interface Response { List<String> getData(); Observable<Response> next(); } public Observable<Response> apiCall(...) {...} public Observable<String> loadAll(Observable<Response> source) { if (source == null) { return Observable.empty(); } else { return source.flatMap(resp -> { List<String> data = resp.getData(); Observable<Response> next = resp.next(); return Observable.concat( Observable.from(data), loadAll(next) ); }); } }

Observable<String> all = loadAll(apiCall(...)) .filter(str -> str.contains("#uamobile")) .take(20) .timeout(1, TimeUnit.MINUTES);

Tip: Use infinite squencesTip: Use infinite squences

ObservableObservable.empty().empty()

concatconcat

Tip: be careful with evaluation timeTip: be careful with evaluation time

Observable<MyHeavyData> dataObservable = loadDataFromNetwork() .timeout(10, TimeUnit.SECONDS) .onErrorResumeNext(Observable.just(loadDataFromStorage()));

Observable<MyHeavyData> dataObservable = loadDataFromNetwork() .timeout(10, TimeUnit.SECONDS) .onErrorResumeNext(ex -> Observable.just(loadDataFromStorage()));

onErrorResumeNext

timeouttimeout

CancelationCancelation

ProactiveProactive

Observable<Integer> items = Observable.create(subscriber -> { int i = 0; while (true) { if(subscriber.isUnsubscribed()) return; subscriber.onNext(i++); } }); items.take(10).subscribe(x -> print(x));

ReactiveReactive

final LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(ctx); Observable<Intent> broadcasts = Observable.create(subscriber -> { final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { subscriber.onNext(intent); } }; final Subscription subscription = Subscriptions.create( () -> localBroadcastManager.unregisterReceiver(broadcastReceiver) ); subscriber.add(subscription); localBroadcastManager.registerReceiver(broadcastReceiver, intentFilter); });

OkHttp exampleOkHttp example

public Observable<String> fromOkCall3(Call call){ return Observable.create(subscriber -> { subscriber.add(Subscriptions.create(() -> call.cancel())); call.enqueue(new Callback() { @Override public void onFailure(Request request, IOException e) { subscriber.onError(e); } @Override public void onResponse(Response response) throws IOException { try { subscriber.onNext(response.body().string()); subscriber.onCompleted(); } catch (IOException e) { subscriber.onError(e); } } }); }); }

Functional CollectionsFunctional CollectionsRight here, in your javaRight here, in your java

Observables can be used to operateover simple collections

Collection as ObservableCollection as Observable

Build-in operatorsNo intermediate allocationsFunctional style

How to useHow to use

1. Create Observable from IterableObservable.from(list)

2. Transform it using build-in operators.filter(...), .map(...), .flatMap(...)

3. Transform to BlockingObservable and get resut.toBlocking.toList().single().toBlocking.single().toBlocking.toIterable()

class GameSession { String user; int score; } List<Pair<String, Integer>> leaderboard = Observable.from(gameSessions) .filter(session -> session.score != 0) .groupBy(session -> session.user) .flatMap(groupedSessions -> groupedSessions.map(session -> session.score) .reduce(0, (n1, n2) -> n1 + n2) .map(totalScore -> Pair.create(groupedSessions.getKey(), totalScore)) ) .take(10) .toSortedList((pair1, pair2) -> pair2.second.compareTo(pair1.second)) .toBlocking() .single();

Example: LeaderboardExample: Leaderboard

groupBygroupBy

reducereduce

Questions?Questions?

LinksLinkshttps://github.com/ReactiveX/RxJava/wikiYour Mouse is a DatabasePrinciples of Reactive ProgrammingTop 7 Tips for RxJava on Android

Recommended