FRP
on
Android
Yaroslav Heriatovych   
FRP
    Functional - functional decomposition, no side effects
    Reactive - data flow, propagation of change
    Programming - the way you do it
Why reactive
Observer pattern


Problems
- Side effects
- Encapsulation
- Composability
- Resource management
- Abstraction
- Semantic distance
Reactive

REACTIVE
Execution context
How to deal with execution context ?
In Android world means:
How to execute heavy tasks on background threads 
and deliver result (or error) in UI thread?
and deliver result (or error) in UI thread?
THREADS
 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();Threads
Pros:
Cons:Simple
Hard way to deliver results in UI threadBroken dataflow
Async task
        new AsyncTask<Void, Integer, String>(){
            @Override
            protected String doInBackground(Void... params) {
                return doHeavyTask();
            }
            @Override
            protected void onPostExecute(String s) {
                showResult(s);
            }
        }.execute();
Pros:
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)
Loaders
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) {} }
Loaders
Pros:
Cons:Deals with main threadDeals with Activity/Fragment lifecycleGood for Cursor fetching
Not composableA lot of boilerplate codeBad for custom background logic
Android Annotations
        @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);
        }Pros:
Cons:Almost no boilerplate
No result propogation (and errors)No background tasks synchronization
So...
Is situation so bad?
And Now for Something Completely Different
RxJava
| 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() | 
RxJava
| 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() | 
RxJava contract
onNext*, (onError | onCompleted)
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 {...}
Observable creation
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();
        }
    });
}
TL;DR: 
When Observer subscribes, emit two strings and complete stream
(respect the contract!).
(respect the contract!).
Observable creation (better)
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);
            }
        }
    });
}Observer
        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);
            }
        });
TL;DR: 
- handle every string;
- log when sequence completes
- print stackTrace in case of error
"Simple" composition
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()); } })); } }); }
TL;DR: on every string count chars and deliver it to observer
Map
Ok, we have map:
     Observable<Integer> charsCounts(Observable<String> strings){
        return strings.map(new Func1<String, Integer>() {
            @Override
            public Integer call(String s) {
                return s.length();
            }
        });
    }Yep, it's easy!
But...

Problem:
Java don't have lamdas
First solution
Use different language
- Scala
- Clojure
- Kotlin
- Xtend
Second solution
Use the same language
Do you know some Java with lambda support?
Java8
So...
We want to use Java8 lamdas on DalvikVM (Java6)
So we need to compile lamdas to something android can understand
And there is project that do exactly what we want!
Retrolambda
How it works
- Compile Java8 code with Java8 javac
- Convert java8 bytecode to java6 bytecode
- Pack new classes back
Rule:
Any functional interface can be converted to lambda
Functional interface is defined as any interface that has exactly one abstract method.
Config
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'
Config
Optional
retrolambda {jdk System.getenv("JAVA8_HOME") javaVersion JavaVersion.VERSION_1_6 } android { compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } }
Java6
        strings.map(new Func1<String, Integer>() {
            @Override
            public Integer call(String s) {
                return s.length();
            }
        });Java8
        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);One more thing...
        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
Bytecode time!
 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       
}    A$1 inner class
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 } 
B class
 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 } 
B$$Lambda$1 Lambda class
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: returnpublic static rx.util.functions.Func2 lambdaFactory$(); Code: 0: getstatic #26 // Field instance:Lfrp/B$$Lambda$1; 3: areturn }
    No reference to outer instance!
No object per call allocation!
RxJava
(again)
        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));(Synchronous)
Image loading
        Observable<Bitmap> imageObservable = Observable.create(observer -> {
            Bitmap bm = downloadBitmap();
            return bm;
        });
        imageObservable.subscribe(image -> loadToImageView(image));But it will block UI thread!
         imageObservable
                .subscribeOn(Schedulers.newThread())
                .subscribe(image -> loadToImageView(image));But it will invoke loadToImageView not UI thread!
         imageObservable
                .subscribeOn(Schedulers.newThread())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(image -> loadToImageView(image));Subscription
        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();
        }Subscription
(old way, before 0.17)
        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;
                    }
                });RxJava basics
Marble diagrams
    map example
    

Observable creation
Main function
Observable.create(Observable.onSubscribe) Other
from( ) — convert an Iterable or a Future or single value into an Observablerepeat( ) — create an Observable that emits a particular item or sequence of items repeatedlytimer( ) — create an Observable that emits a single item after a given delayempty( ) — create an Observable that emits nothing and then completeserror( ) — create an Observable that emits nothing and then signals an errornever( ) — create an Observable that emits nothing at all
Observable transformation
- map( ) — transform the items emitted by an Observable by applying a function to each of them
- flatMap( ) — transform the items emitted by an Observable into Observables, then flatten this into a single Observable
- scan( ) — apply a function to each item emitted by an Observable, sequentially, and emit each successive value
- groupBy( ) and groupByUntil( ) — divide an Observable into a set of Observables that emit groups of items from the original Observable, organized by key
- buffer( ) — periodically gather items from an Observable into bundles and emit these bundles rather than emitting the items one at a time
- 
window( ) — periodically subdivide items from an Observable into Observable windows and emit these windows rather than emitting the items one at a time 
 
Map

flatMap
map, then flatten

scan

groupBy

buffer

Observable filtering
- filter( ) — filter items emitted by an Observable
- takeLast( ) — only emit the last n items emitted by an Observable
- takeLastBuffer( ) — emit the last n items emitted by an Observable, as a single list item
- skip( ) — ignore the first n items emitted by an Observable
- take( ) — emit only the first n items emitted by an Observable
- first( ) — emit only the first item emitted by an Observable, or the first item that meets some condition
- elementAt( ) — emit item n emitted by the source Observable
- timeout( ) — emit items from a source Observable, but issue an exception if no item is emitted in a specified timespan
- distinct( ) — suppress duplicate items emitted by the source Observable
filter

skip

take

timeout

distinct

distinctUntilChanged

Cold Observables
        Observable<String> cold = Observable.create(observer ->{
            observer.onNext("Hello");
            observer.onNext("world");
        });Will generate items on every subscription
(lazy)
Subject
Observer + Observable
Channel
"Promise" for Observables

PublishSubject
        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
        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);
4 Subjects

Android Examples
Background task
    Observable.create((Subscriber<? super Bitmap> subscriber) -> {
            try{
                Bitmap image = loadImage();
                subscriber.onNext(image);
                subscriber.onCompleted();
            }catch (Exception ex){
                subscriber.onError(ex);
            }
    }).subscribeOn(Schedulers.io());Can be simplified to 
 Async.start(() -> loadImage(), Schedulers.io());
    or use 
Schedulers.computation() by default
    
 Async.start(() -> calculateSomething());Convert view to
Observable of clicks
    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"));Event composition
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));
- No state
- No namespace pollution
- No condition branches
No Loaders
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; } }
List Fragment
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)); } }
Drawing example
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); } }
Drawing example
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; } }
Multitouch
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; }
Retrofit support
@GET("/user/{id}/photo")
Observable<Photo> getUserPhoto(@Path("id") int id);Links
Questions?
FRP on Android
By Yaroslav Heriatovych
FRP on Android
Small introduction how to use FRP principles with RxJava library in modern Android applications
- 49,535
 
 
   
  