Yaroslav Heriatovych
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()
|
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 {...}
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();
}
});
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)
}
});
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)
}
});
not Framework
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
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));
Once more:
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
}
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
}
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);
}
}
}
});
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());
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);
}
}
});
});
}
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();
}
});
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
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 =(
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();
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);
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
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));
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);
});
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);
}
}
});
});
}
Observables can be used to operate
over simple collections
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();
List<GameSession> gameSessions = null;
List<Pair<String, Integer>> leaderboard =
Observable.from(gameSessions)
.filter(session -> session.score != 0)
.groupBy(session -> session.user)
.flatMap(groupedSessions ->
groupedSessions.publish(shared -> {
Observable<Integer> total =
shared.map(session -> session.score)
.reduce(0, (n1, n2) -> n1 + n2);
Observable<Integer> count = shared.count();
return total.zipWith(count, (t, c) -> t/c);
})
.map(totalScore ->
Pair.create(groupedSessions.getKey(), totalScore))
)
.take(10)
.toSortedList((pair1, pair2) -> pair2.second.compareTo(pair1.second))
.toBlocking()
.single();
(average)
loadDataFromNetwork()
.timeout(10, TimeUnit.SECONDS)
build-in
.timeout(10, TimeUnit.SECONDS, loadDataFromStorageObservable());
loadDataFromNetwork()
.timeout(10, TimeUnit.SECONDS)
.retry()
build-in
.retry(5)
.retry((attempt, throwable) ->
attempt < 5 && throwable instanceof UnknownHostException)
.retryWhen(throwables ->
throwables.zipWith(Observable.range(1, 5), (ex, i) -> i)
.map(i -> (int) Math.pow(2, i))
.flatMap(time -> Observable.timer(time, TimeUnit.SECONDS))
.concatWith(throwables.flatMap(t -> Observable.error(t)))
);
ViewObservable.text(editText)
.map(textChangeEvent -> textChangeEvent.text)
.debounce(500, TimeUnit.MILLISECONDS)
.switchMap(text -> loadSuggestions(text))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(suggestions -> showSuggestions(suggestions));
debounce and autocomplete
ArrayAdapter<Message> messagesAdapter = ...;
Observable<Message> messagesStream = getMessagesStream();
messagesStream
.observeOn(AndroidSchedulers.mainThread())
.subscribe(message -> messagesAdapter.add(message));
messagesStream
.buffer(10) //collect lists by 10
.observeOn(AndroidSchedulers.mainThread())
.subscribe((List<Message> messageList) ->
messagesAdapter.addAll(messageList));
Ineficient!
messagesStream
.buffer(2, TimeUnit.SECONDS) //collect every 2 seconds
.observeOn(AndroidSchedulers.mainThread())
.subscribe((List<Message> messageList) ->
messagesAdapter.addAll(messageList));
messagesStream
.publish(sharedMessagesObs -> {
Observable<Message> boundary =
sharedMessagesObs.debounce(1, TimeUnit.SECONDS);
return sharedMessagesObs
.buffer(boundary);
})//wait until there are no items for 2 seconds
//then propagate them together
.observeOn(AndroidSchedulers.mainThread())
.subscribe((List<Message> messageList) ->
messagesAdapter.addAll(messageList));
Observable<List<TimeLineItem>> timeline = ...;
Observable<Boolean> busyList = Observable.create(subscriber -> {
mTimelineListView.setOnTouchListener((v, event) -> {
int action = event.getActionMasked();
subscriber.onNext(action == MotionEvent.ACTION_DOWN
|| action == MotionEvent.ACTION_MOVE);
v.onTouchEvent(event);
return true;
});
});
Observable<List<TimeLineItem>> delayedTimeline = Observable.combineLatest(
timeline,
busyTimeline.distinctUntilChanged(),
(items, isBusy) -> isBusy ? null : items
).filter(items -> items != null);