Reactive Functional Programming with Java 8
on Android NShipeng Xu
May 6th 2016
What is Reactive Programming?
Observer Pattern
An Observable emits items. A Subscriber consumes those items.
(from RxJava in practice)
Observable Subscriber
Items
Observable & Subscriber
Observable Transform
Items
Subscriber
Observable & Subscriber
Why Reactive Programming?
Quick example• Find all png images under a folder
• Load the images into a gallery view
http://gank.io/post/560e15be2dca930e00da1083
new Thread() { @Override public void run() { super.run(); for (Folder folder : folders) { File[] files = folder.listFiles(); for (File file : files) { if (file.getName().endsWith(".png")) { final Bitmap bitmap = getBitmapFromFile(file); getActivity().runOnUiThread(new Runnable() { @Override public void run() { imageCollectorView.addImage(bitmap); } }); } } } } }.start();
Vanilla Java
http://gank.io/post/560e15be2dca930e00da1083
Observable.from(folders) .flatMap((folder) -> Observable.from(folder.listFiles()) ) .filter((file) -> file.getName().endsWith(".png") ) .map((file) -> getBitmapFromFile(file) ) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe((bitmap) -> imageCollectorView.addImage(bitmap) );
RxJava
Create an Observable
Observable observable = Observable.create(new Observable.OnSubscribe<String>() { @Override public void call(Subscriber<? super String> subscriber) { subscriber.onNext("Hello"); subscriber.onNext("World"); subscriber.onCompleted(); } });
observable.subscribe(subscriber);To subscribe to an observable:
Observable.just("Hello", "World")
Or the shorter version:
Subscriber<String> subscriber = new Subscriber<String>() { @Override public void onNext(String s) { Log.d(tag, "Item: " + s); }
@Override public void onCompleted() { Log.d(tag, "Completed!"); }
@Override public void onError(Throwable e) { Log.d(tag, "Error!"); } };
Subscriber Sample
http://reactivex.io/documentation/observable.htmlhttp://rxmarbles.com/
Demo project
Get started with Java 8 on Android N
android { compileSdkVersion 'android-N' buildToolsVersion "24.0.0 rc1" defaultConfig { applicationId "me.billhsu.rxdemo" minSdkVersion 'N' targetSdkVersion 'N' versionCode 1 versionName "1.0" jackOptions { enabled true } } compileOptions { targetCompatibility 1.8 sourceCompatibility 1.8 } }
Rx libraries for AndroidRxAndroid - Provide a Scheduler that schedules on the main thread or any given Looper. RxLifecycle - Lifecycle handling APIs for Android apps using RxJava RxBinding - RxJava binding APIs for Android's UI widgets. SqlBrite - A lightweight wrapper around SQLiteOpenHelper and ContentResolver which introduces reactive stream semantics to queries. Android-ReactiveLocation - Library that wraps location play services API boilerplate with a reactive friendly API. rx-preferences - Reactive SharedPreferences for Android RxFit - Reactive Fitness API Library for Android RxWear - Reactive Wearable API Library for Android RxPermissions - Android runtime permissions powered by RxJava RxNotification - Easy way to register, remove and manage notifications using RxJava
Android Scheduler
Schedulers.io() Schedulers.computation() Schedulers.newThread() Schedulers.from(Executor) Schedulers.immediate() Schedulers.trampoline()
Observable.just("Hello", "World") .subscribeOn(Schedulers.newThread()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(/* update UI*/);
REST Responses to Observables
public interface GitHubApi { @GET("users/{users}/followers") Observable<List<GitHubUser>> getFollowers(@Path("users") String user);
@GET("users/{users}") Observable<GitHubUser> getUser(@Path("users") String user); }
private void setupRetrofit() { OkHttpClient client = new OkHttpClient(); client.setConnectTimeout(5, TimeUnit.SECONDS); Retrofit retrofit = new Retrofit.Builder() .client(client) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) .baseUrl("https://api.github.com/") .build(); gitHubApi = retrofit.create(GitHubApi.class); }
https://api.github.com/users/billhsu
RxView.clicks(button).subscribe((a) -> { button.setClickable(false); adapter.getGitHubUserList().clear(); adapter.notifyDataSetChanged(); progressBar.setVisibility(View.VISIBLE); gitHubApi.getFollowers(userName.getText().toString()) .flatMapIterable(users -> users) .flatMap(user -> gitHubApi.getUser(user.getLogin())) .filter(user -> !TextUtils.isEmpty(user.getCompany())) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe( user -> { adapter.getGitHubUserList().add(user); adapter.notifyDataSetChanged(); }, error -> { Toast.makeText(MainActivity.this, error.toString(), Toast.LENGTH_LONG).show(); button.setClickable(true); progressBar.setVisibility(View.GONE); }, () -> { button.setClickable(true); progressBar.setVisibility(View.GONE); }); });
The click stream
Click stream to GitHubUser Stream
RxView.clicks(button).subscribe((a) -> { button.setClickable(false); adapter.getGitHubUserList().clear(); adapter.notifyDataSetChanged(); progressBar.setVisibility(View.VISIBLE); gitHubApi.getFollowers(userName.getText().toString()) .flatMapIterable(users -> users) .flatMap(user -> gitHubApi.getUser(user.getLogin())) .filter(user -> !TextUtils.isEmpty(user.getCompany())) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe( user -> { adapter.getGitHubUserList().add(user); adapter.notifyDataSetChanged(); }, error -> { Toast.makeText(MainActivity.this, error.toString(), Toast.LENGTH_LONG).show(); button.setClickable(true); progressBar.setVisibility(View.GONE); }, () -> { button.setClickable(true); progressBar.setVisibility(View.GONE); }); });
Subscribe to GitHubUser Stream
RxView.clicks(button).subscribe((a) -> { button.setClickable(false); adapter.getGitHubUserList().clear(); adapter.notifyDataSetChanged(); progressBar.setVisibility(View.VISIBLE); gitHubApi.getFollowers(userName.getText().toString()) .flatMapIterable(users -> users) .flatMap(user -> gitHubApi.getUser(user.getLogin())) .filter(user -> !TextUtils.isEmpty(user.getCompany())) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe( user -> { adapter.getGitHubUserList().add(user); adapter.notifyDataSetChanged(); }, error -> { Toast.makeText(MainActivity.this, error.toString(), Toast.LENGTH_LONG).show(); button.setClickable(true); progressBar.setVisibility(View.GONE); }, () -> { button.setClickable(true); progressBar.setVisibility(View.GONE); }); });
Summary