, 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