RxJava, Tips and Tricks
Yaroslav Heriatovych
RxJava
Background
Observable
Observable
Observables fill the gap by being the ideal implementation of access to asynchronous sequences of multiple items | ||
---|---|---|
single items | multiple items | |
synchronous |
T getData()
|
Iterable<T> getData()
|
asynchronous |
Future<T> getData()
|
Observable<T> getData()
|
Primitives
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 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 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)
}
});
Schedulers
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,
not Framework
Lambda
the new old thing
Used 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 the noise
More code - more bugs
Map
Filter
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 noise
- Clear logic
- Concise
Once more:
- Java8 for android development
- Native support in Android Studio
- Magic
One more thing
It'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 thing
It'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 abstraction
to 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 stream
take(n)
Abstract 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());
flatMap
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);
}
}
});
});
}
Lazyness
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) 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 all above
Cold 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 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 & 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 squences
Observable.empty()
concat
Tip: 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
timeout
Cancelation
Proactive
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));
Reactive
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 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 Collections
Right here, in your java
Observables can be used to operate
over simple collections
Collection as Observable
- Build-in operators
- No intermediate allocations
- Functional style
How to use
-
Create Observable from Iterable
Observable.from(list) -
Transform it using build-in operators
.filter(...), .map(...), .flatMap(...) -
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: Leaderboard
groupBy
reduce
Questions?
Links
RxJava, Tips and Tricks (Edit)
By Yaroslav Heriatovych
RxJava, Tips and Tricks (Edit)
Some tips and trick about using RxJava on Android platform.
- 4,298