How to deal with execution context ?
Handler handler = new Handler();
new Thread(){
@Override
public void run() {
final String result = doHeavyTask();
handler.post(new Runnable() {
@Override
public void run() {
showResult(result);
}
});
}
}.start();
Cons:Simple
Hard way to deliver results in UI threadBroken dataflow
new AsyncTask<Void, Integer, String>(){
@Override
protected String doInBackground(Void... params) {
return doHeavyTask();
}
@Override
protected void onPostExecute(String s) {
showResult(s);
}
}.execute();
Cons:Deal with main thread
No error propogation to main threadNot bound to activity/fragment lifecycleNot composable
(don't tell me about nested async tasks)
class MyFragment extends Fragment implements LoaderManager.LoaderCallbacks<String> { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getLoaderManager().initLoader(42, null, this); } @Override public Loader<String> onCreateLoader(int id, Bundle args) { return new AsyncTaskLoader<String>(getActivity()) { @Override public String loadInBackground() { return doHeavyTask(); } }; }
@Override public void onLoadFinished(Loader<String> loader, String data) { showResult(data); } @Override public void onLoaderReset(Loader<String> loader) {} }
Cons:Deals with main threadDeals with Activity/Fragment lifecycleGood for Cursor fetching
Not composableA lot of boilerplate codeBad for custom background logic
@Background
void doSomethingInBackground() {
// do something
MyResult result = XXX;
updateUI(result);
}
// Notice that we manipulate the activity ref only from the UI thread
@UiThread
void updateUI(MyResult result) {
activity.showResult(result);
}
Cons:Almost no boilerplate
No result propogation (and errors)No background tasks synchronization
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()
|
An Observable is the asynchronous/push "dual" to the synchronous/pull Iterable | ||
---|---|---|
event | Iterable (pull) | Observable (push) |
retrieve data | T next() |
onNext(T) |
discover error | throws Exception
|
onError(Exception) |
complete | returns | onCompleted() |
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 {...}
public Observable<String> getStrings(){
return Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> subscriber) {
subscriber.onNext("Hello");
subscriber.onNext("World");
subscriber.onCompleted();
}
});
}
public Observable<String> getStrings() {
return Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> subscriber) {
try {
subscriber.onNext("Hello");
subscriber.onNext("World");
subscriber.onCompleted();
} catch (Exception ex) {
subscriber.onError(ex);
}
}
});
}
Observable<String> strings = getStrings();
Subscription subsctiption = strings.subscribe(new Observer<String>() {
@Override
public void onCompleted() {
Log.d("rx", "no more data");
}
@Override
public void onError(Throwable throwable) {
throwable.printStackTrace();
}
@Override
public void onNext(String s) {
showResult(s);
}
});
- handle every string;
- log when sequence completes
- print stackTrace in case of error
Observable<Integer> charsCounts(Observable<String> strings){ return Observable.create(new Observable.OnSubscribe<Integer>() { @Override public void call(Subscriber<? super Integer> subscriber) { subscriber.add(strings.subscribe(new Observer<String>() { @Override public void onCompleted() { subscriber.onCompleted(); } @Override public void onError(Throwable throwable) { subscriber.onError(throwable); }
@Override public void onNext(String s) { //actual transformation subscriber.onNext(s.length()); } })); } }); }
Observable<Integer> charsCounts(Observable<String> strings){
return strings.map(new Func1<String, Integer>() {
@Override
public Integer call(String s) {
return s.length();
}
});
}
Functional interface is defined as any interface that has exactly one abstract method.
buildscript {
repositories { mavenCentral() maven { url "https://oss.sonatype.org/content/repositories/snapshots" } } dependencies { classpath 'me.tatarka:gradle-retrolambda:1.2.+' } } // Required because retrolambda is on maven central repositories { mavenCentral() } apply plugin: 'android' apply plugin: 'retrolambda'
retrolambda {
jdk System.getenv("JAVA8_HOME") javaVersion JavaVersion.VERSION_1_6 } android { compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } }
strings.map(new Func1<String, Integer>() {
@Override
public Integer call(String s) {
return s.length();
}
});
strings.map((String s) -> {
return s.length();
});
strings.map((String s) -> s.length());
strings.map((s) -> s.length());
strings.map(s -> s.length());
strings.map(String::length);
numbers.reduce(new Func2<Integer, Integer, Integer>() {
@Override
public Integer call(Integer i1, Integer i2) {
return i1 + i2;
}
});
!=
numbers.reduce((i1, i2) -> i1 + i2);
So, take quick look on bytecode
public class A {
public Observable<Integer> foo(Observable<Integer> numbers){
return numbers.reduce(new Func2<Integer, Integer, Integer>() {
@Override
public Integer call(Integer i1, Integer i2) {
return i1 + i2;
}
});
}
}
public class frp.A {
public frp.A();
Code:
0: aload_0
1: invokespecial #11 // Method java/lang/Object."":()V
4: return
public rx.Observable foo(rx.Observable);
Code:
0: aload_1
1: new #7 // class frp/A$1
4: dup
5: aload_0
6: invokespecial #18 // Method frp/A$1."":(Lfrp/A;)V
9: invokevirtual #24 // Method rx/Observable.reduce:(Lrx/util/functions/Func2;)Lrx/Observable;
12: areturn
}
class frp.A$1 implements rx.util.functions.Func2
{ final frp.A this$0; frp.A$1(frp.A); Code: 0: aload_0 1: aload_1 2: putfield #19 // Field this$0:Lfrp/A; 5: aload_0 6: invokespecial #22 // Method java/lang/Object." public java.lang.Integer call(java.lang.Integer, java.lang.Integer);":()V 9: return
Code: 0: aload_1 1: invokevirtual #32 // Method java/lang/Integer.intValue:()I 4: aload_2 5: invokevirtual #32 // Method java/lang/Integer.intValue:()I 8: iadd 9: invokestatic #36 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 12: areturn public java.lang.Object call(java.lang.Object, java.lang.Object); Code: 0: aload_0 1: aload_1 2: checkcast #28 // class java/lang/Integer 5: aload_2 6: checkcast #28 // class java/lang/Integer 9: invokevirtual #42 // Method call:(Ljava/lang/Integer;Ljava/lang/Integer;)Ljava/lang/Integer; 12: areturn }
public class B {
public Observable foo(Observable numbers){
return numbers.reduce((i1, i2) -> i1 + i2);
}
}
public class frp.B { public frp.B(); Code: 0: aload_0 1: invokespecial #14 // Method java/lang/Object."
":()V 4: return public rx.Observable foo(rx.Observable ); Code: 0: aload_1 1: invokestatic #24 // Method frp/B$$Lambda$1.lambdaFactory$:()Lrx/util/functions/Func2; 4: invokevirtual #30 // Method rx/Observable.reduce:(Lrx/util/functions/Func2;)Lrx/Observable; 7: areturn
static java.lang.Integer lambda$foo$0(java.lang.Integer, java.lang.Integer); Code: 0: aload_0 1: invokevirtual #41 // Method java/lang/Integer.intValue:()I 4: aload_1 5: invokevirtual #41 // Method java/lang/Integer.intValue:()I 8: iadd 9: invokestatic #45 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 12: areturn }
final class frp.B$$Lambda$1 implements rx.util.functions.Func2 { public java.lang.Object call(java.lang.Object, java.lang.Object); Code: 0: aload_1 1: checkcast #14 // class java/lang/Integer 4: aload_2 5: checkcast #14 // class java/lang/Integer 8: invokestatic #20 // Method frp/B.lambda$foo$0: (Ljava/lang/Integer;Ljava/lang/Integer;)Ljava/lang/Integer;
11: areturn static {}; Code: 0: new #2 // class frp/B$$Lambda$1 3: dup 4: invokespecial #24 // Method "<init>":()V 7: putstatic #26 // Field instance:Lfrp/B$$Lambda$1; 10: return
public static rx.util.functions.Func2 lambdaFactory$(); Code: 0: getstatic #26 // Field instance:Lfrp/B$$Lambda$1; 3: areturn }
Observable<String> strings = Observable.create(
(Subscriber<? super String> subscriber) -> {
subscriber.onNext("Hello");
subscriber.onNext("World");
subscriber.onCompleted();
}
);
strings.map(str -> str.length())
.subscribe(length -> toast("length is " + length));
Observable<Bitmap> imageObservable = Observable.create(observer -> {
Bitmap bm = downloadBitmap();
return bm;
});
imageObservable.subscribe(image -> loadToImageView(image));
imageObservable
.subscribeOn(Schedulers.newThread())
.subscribe(image -> loadToImageView(image));
imageObservable
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(image -> loadToImageView(image));
Observable<String> strings = Observable.create(
(Subscriber<? super String> subscriber) -> {
while (!subscriber.isUnsubscribed()){
String result = doHeavyTask();
subscriber.onNext(result);
}
});
Subscription subscription = strings.subscribe(str -> log(s));
public void onDestroy() {
super.onDestroy();
subscription.unsubscribe();
}
Observable<String> stringsNew = Observable.create(
new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> subscriber) {
subscriber.onNext("Hello");
if (subscriber.isUnsubscribed()) return;
subscriber.onNext("World");
subscriber.onCompleted();
}
});
Observable<String> stringsOld = Observable.create(
new Observable.OnSubscribeFunc<String>() {
@Override
public Subscription onSubscribe(Observer<? super String> observer) {
Subscription subscription = BooleanSubscription.create();
observer.onNext("Hello");
if (subscription.isUnsubscribed())
return subscription; //Will not work
observer.onNext("World");
observer.onCompleted();
return subscription;
}
});
Observable.create(Observable.onSubscribe)
from( ) — convert an Iterable or a Future or single value into an Observable
repeat( ) — create an Observable that emits a particular item or sequence of items repeatedly
timer( ) — create an Observable that emits a single item after a given delay
empty( ) — create an Observable that emits nothing and then completes
error( ) — create an Observable that emits nothing and then signals an error
never( ) — create an Observable that emits nothing at all
Observable<String> cold = Observable.create(observer ->{
observer.onNext("Hello");
observer.onNext("world");
});
PublishSubject<Integer> channel = PublishSubject.create();
Subscription a = channel.subscribe(x -> toast("a: "+x));
Subscription b = channel.subscribe(x -> toast("b: "+x));
channel.onNext(42);
a.unsubscribe();
channel.onNext(4711);
channel.onCompleted();
Subscription c = channel.subscribe(x -> toast("c: "+x));
channel.onNext(13);
ReplaySubject<Integer> channel = ReplaySubject.create();
Subscription a = channel.subscribe(x -> toast("a: "+x));
Subscription b = channel.subscribe(x -> toast("b: "+x));
channel.onNext(42);
a.unsubscribe();
channel.onNext(4711);
channel.onCompleted();
Subscription c = channel.subscribe(x -> toast("c: "+x));
channel.onNext(13);
Observable.create((Subscriber<? super Bitmap> subscriber) -> {
try{
Bitmap image = loadImage();
subscriber.onNext(image);
subscriber.onCompleted();
}catch (Exception ex){
subscriber.onError(ex);
}
}).subscribeOn(Schedulers.io());
Async.start(() -> loadImage(), Schedulers.io());
Schedulers.computation()
by default
Async.start(() -> calculateSomething());
public static <T extends View> Observable<T> clicksFrom(T view) {
PublishSubject publishSubject = PublishSubject.create();
view.setOnClickListener((v) -> publishSubject.onNext(view));
return publishSubject.asObservable();
}
clicksFrom(button).subscribe(view -> toast("clicked"));
final ImageView imageView = (ImageView) findViewById(R.id.image);
Observable<Bitmap> imageObservable = Async.start(() -> loadImage(), Schedulers.io()) .cache() .observeOn(AndroidSchedulers.mainThread()); //show loaded imageObservable imageObservable.subscribe((Bitmap image) -> imageView.setImageBitmap(image));
//handle imageView clicks imageObservable .flatMap(image -> clicksFrom(imageView).map((ImageView view) -> image)) .subscribe((Bitmap image) -> processImage(image));
class NoLoadersFragment extends Fragment{ ImageView imageView; Observable<Bitmap> imageObservable; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setRetainInstance(true); imageObservable = Async.start(() -> loadImage(), Schedulers.io()) .cache() .observeOn(AndroidSchedulers.mainThread()); }
@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fragment_processing, container, false); imageView = (ImageView) rootView.findViewById(R.id.image);
imageObservable.subscribe(image -> imageView.setImageBitmap(image)); return rootView; } }
Observable<String> loadUrl(String url){...}; Observable<Message> parse(String json){...}; class AsyncListFragment extends ListFragment{ Observable<Message> messages; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setRetainInstance(true); String url = getArguments().getString("url"); messages = loadUrl(url) .flatMap(json -> parse(json)) .cache() .observeOn(AndroidSchedulers.mainThread()); }
@Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); MessagesAdapter adapter = new MessagesAdapter(); messages.first().subscribe(any -> setListAdapter(adapter)); messages.subscribe(message -> adapter.add(message)); } }
class CanvasView extends View { private final PublishSubject<MotionEvent> motionSubject = PublishSubject.create(); public final Observable<MotionEvent> motions = motionSubject.asObservable(); private final Paint paint; private Set<Path> paths = new HashSet<>(); public CanvasView(Context context) { super(context); paint = new Paint(); paint.setColor(context.getResources().getColor(android.R.color.black)); paint.setStyle(Paint.Style.STROKE); paint.setAntiAlias(true); setOnTouchListener((View v, MotionEvent event) -> { motionSubject.onNext(event); return true; }); }
public void addPath(Path path) { paths.add(path); this.invalidate(); } @Override protected void onDraw(Canvas canvas) { for (Path path : paths) canvas.drawPath(path, paint); } }
public class CanvasFragment extends Fragment { CanvasView canvas; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { canvas = new CanvasView(getActivity());
Observable<MotionEvent> downs = canvas.motions.filter(ev -> ev.getActionMasked() == MotionEvent.ACTION_DOWN); Observable<MotionEvent> ups = canvas.motions.filter(ev -> ev.getActionMasked() == MotionEvent.ACTION_UP); Observable<MotionEvent> moves = canvas.motions.filter(ev -> ev.getActionMasked() == MotionEvent.ACTION_MOVE); Observable<Path> pathObservable = downs.flatMap(downEvent -> { Path startPath = new Path(); startPath.moveTo(downEvent.getX(), downEvent.getY()); return moves.takeUntil(ups) .scan(startPath, (currentPath, movePoint) -> { currentPath.lineTo(movePoint.getX(), movePoint.getY()); return currentPath; }); });
pathObservable.subscribe(path -> canvas.addPath(path)); return canvas; } }
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { canvas = new CanvasView(getActivity()); Observable<MotionEvent> allDowns = canvas.motions.filter(ev -> ev.getActionMasked() == ACTION_DOWN || ev.getActionMasked() == ACTION_POINTER_DOWN); Observable<MotionEvent> allMoves = canvas.motions.filter(ev -> ev.getActionMasked() == ACTION_MOVE); Observable<MotionEvent> allUps = canvas.motions.filter(ev -> ev.getActionMasked() == ACTION_UP || ev.getActionMasked() == ACTION_POINTER_UP); Observable<Path> pathObservable = allDowns .flatMap(downEvent -> { int downIndex = downEvent.getActionIndex(); int fingerId = downEvent.getPointerId(downIndex); Observable<MotionEvent> ups = allUps.filter(evt -> evt.getPointerId(evt.getActionIndex()) == fingerId);
Path path = new Path(); path.moveTo(downEvent.getX(downIndex), downEvent.getY(downIndex)); return allMoves.filter(evt -> evt.findPointerIndex(fingerId) != -1).takeUntil(ups) .scan(path, (currentPath, movePoint) -> { int moveIndex = movePoint.findPointerIndex(fingerId); currentPath.lineTo(movePoint.getX(moveIndex), movePoint.getY(moveIndex)); return currentPath; }); }); pathObservable.subscribe(path -> canvas.addPath(path)); return canvas; }
@GET("/user/{id}/photo")
Observable<Photo> getUserPhoto(@Path("id") int id);