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






Deprecating the Observer Pattern

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?

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:
Simple 
Cons:
Hard way to deliver results in UI thread
Broken 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:
Deal with main thread
Cons:
No error propogation to main thread
Not bound to activity/fragment lifecycle
Not 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:
Deals with main thread 
Deals with Activity/Fragment lifecycle
Good for Cursor fetching 
Cons:
Not composable
A lot of boilerplate code
Bad 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:
Almost no boilerplate
Cons:
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!).


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


  1. Compile Java8 code with Java8 javac
  2. Convert java8 bytecode to java6 bytecode
  3. 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."":()V
       9: return        
  public java.lang.Integer call(java.lang.Integer, java.lang.Integer);    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: return        
  public 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 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 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?

Made with Slides.com