View
15
Download
2
Embed Size (px)
Citation preview
Reactive Programming RxJava + Android
Streams (data flow)
Reactive eXtensions ?
Rx Observer pattern• Observable • Observer/Subscriber• Subscription• Subject
onNext, onCompleted, and onErrornew Observer<T>() { @Override public void onNext(T element) { // called when Observable «pushes» new event. }
@Override public void onCompleted() { // when stream is successfully completed(terminal state) }
@Override public void onError(Throwable e) { // bad news, stream is ends with exception (terminal state) }};
Rx Basics
http://reactivex.io/
- Emit 0 or N elements- Fails with exception -> onError()- Normally completes -> onCompleted()
Observable – Observer Contract
Stream creationpublic static Observable<String> helloWorldStream() { return Observable.create(new Observable.OnSubscribe<String>() { @Override public void call(Subscriber<? super String> subscriber) { if (!subscriber.isUnsubscribed()) { try { subscriber.onNext("Hello"); subscriber.onNext("World"); subscriber.onCompleted(); } catch (Exception ex) { subscriber.onError(ex); } } } });}
Stream creationpublic static Observable<String> helloWorldStream() { return Observable.create(subscriber -> { if (!subscriber.isUnsubscribed()) { try { subscriber.onNext("Hello"); subscriber.onNext("World"); subscriber.onCompleted(); } catch (Exception ex) { subscriber.onError(ex); } } });}
Hello World@Test public void helloWorldTest() { Observable<String> stream = helloWorldStream(); stream.subscribe(result -> { System.out.println(result); }, err -> { err.printStackTrace(); });}
Hello World@Test public void helloWorldTest() { Observable<String> stream = helloWorldStream(); stream.subscribe(result -> { System.out.println(result); }, err -> { err.printStackTrace(); });}
Hello World@Test public void helloWorldTest() { Observable<String> stream = helloWorldStream(); stream.subscribe(result -> { System.out.println(result); }, err -> { err.printStackTrace(); });}
Hello World@Test public void helloWorldTest() { Observable<String> stream = helloWorldStream(); stream.subscribe(result -> { System.out.println(result); }, err -> { err.printStackTrace(); });}
Hello World@Test public void helloWorldTest() { Observable<String> stream = helloWorldStream(); stream.subscribe(result -> { System.out.println(result); }, err -> { err.printStackTrace(); });}
"D:\JVM\Java JDK\bin\java"
Hello World
Process finished with exit code 0
Hello World@Test public void helloWorldTest() { Observable<String> stream = helloWorldStream(); stream .reduce((prev, newOne) -> prev + " " + newOne ) .subscribe(result -> { System.out.println(result); }, err -> { err.printStackTrace(); });}
"D:\JVM\Java JDK\bin\java"
Hello World
Process finished with exit code 0
OperatorsCreating – create, defer, from, interval, timer, start . . .Transforming – flatMap, map, scan, groupBy, buffer . . .Filtering – filter, debounce, distinct, take , first, egnoreElements . . .Combining – combineLatest, merge, startWith, switch, zip . . .Conditional & Boolean – contains, skipWhile, takeWhile, takeUntil, all . . .Math & Agregation – reduce, sum, min, max, count . . . And others . . .
http://reactivex.io/documentation/operators.html
Creating operatorsObservable.just(1, 2, 3, 4);
Observable.from(Arrays.asList(“zero", "one", "two", "three"));
Observable.interval(200, TimeUnit.MILLISECONDS);
Observable.error(new Exception(" :`( "));
Transforming operatorsObservable.just(2, 4, 6, 8, 10, 25, 43) .map(num -> new Pair<Integer, Double>(num, Math.sqrt(num))) .subscribe(pair -> println("new element is -> " + pair));
Observable.just(2, 4, 6, 8, 10, 25, 43) .scan((x, y ) -> x + y) .subscribe(sum -> println("Sum is: (" + sum + ")"));
Filtering operatorsObservable.just(2, 30, 22, 5, 60, 1) .filter(num -> num > 10) .subscribe(num -> println("Filtered -> " + num));
Combining operatorsObservable<Integer> ones = Observable.just(1, 1, 1);Observable<Integer> twos = Observable.just(2, 2);Observable .concat(ones, twos) .subscribe(res -> System.out.println(res));
Combining operatorsObservable<Integer> ones = Observable.just(1, 1, 1);Observable<Integer> twos = Observable.just(2, 2);Observable .combineLatest(ones, twos, (o, t) -> "1 stream ->" + o + "\n 2 stream -> " + t) .subscribe(res -> System.out.println(res));
Conditional & Boolean operators
Observable<Integer> zeros = Observable.just(0, 0, 0).delay(200, MILLISECONDS);Observable<Integer> nums = Observable.just(1, 2, 3).delay(100, MILLISECONDS);Observable<Integer> dozens = Observable.just(10, 20, 60).delay(300, MILLISECONDS);
Observable.amb(zeros, nums, dozens) .subscribe(res -> System.out.println(res)); // prints 1 2 3
RxBuspublic class RxBus { private RxBus() { throw new RuntimeException("Not allowed to create instance ");}; private static final Subject<Object, Object> bus = new SerializedSubject<>(PublishSubject.create());
public static void send(Object o) { bus.onNext(o); }
public static Observable<Object> toObserverable() { return bus.asObservable(); }
public static boolean hasObservers() { return bus.hasObservers(); }
public static <T>Observable<T> observeEvent(Class<T> clazz) { return bus.ofType(clazz).asObservable(); }}
RxBuspublic class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); RxBus.send(new ActivityCreatedEvent()); // . . . more logic here
}}
RxBuspublic class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); RxBus.send(new ActivityCreatedEvent()); // . . . more logic here}
class SuperBusyService extends Service { private final CompositeSubscription compSub = new CompositeSubscription(); @Override public void onCreate() { super.onCreate(); compSub.add(RxBus.observEvent(ActivityCreatedEvent.class) .subscribe(event -> doSomeLongOperation(), err -> handleErr(err))); }
@Override public void onDestroy() { super.onDestroy(); compSub.clear(); } . . .
Simple Timer
Simple Timerpublic void launchTicker() { final Long secondsDisabled = 60L; // user not allowed to send request for new sms tickerSub = Observable.interval(1, TimeUnit.SECONDS) .takeUntil(it -> it >= secondsDisabled -1 /* starts from 0 */ ) .map (sec -> (secondsDisabled - sec)) .observeOn(AndroidSchedulers.mainThread()) .doOnSubscribe (() -> llSendAgain.isEnabled = false ) .doOnCompleted (() -> llSendAgain.isEnabled = true) .subscribe(time -> tvTicker.setText(getString(R.string.sms_send_again_timer, time)) , err -> handleError(err));
}
«Do» operators in actionpublic class HomeScreenPresenter { final HomeView view; final HomeInteractor interactor = new HomeInteractorImpl();
public HomeScreenPresenter(HomeView view){ this.view = view; }
public void getClientData(String token) { view.showProgress(); view.manageSubscription(interactor.loadClientData(token) .observeOn(AndroidSchedulers.mainThread()) .subscribeOn(Schedulers.io()) .subscribe(result -> { view.hideProgress(); view.onDataReady(result); }, err -> { view.hideProgress(); view.handleError(err); }) ); }}
«Do» operators in actionpublic class HomeScreenPresenter { final HomeView view; final HomeInteractor interactor = new HomeInteractorImpl();
public HomeScreenPresenter(HomeView view){ this.view = view; }
public void getClientData(String token) { view.showProgress(); view.manageSubscription(interactor.loadClientData(token) .observeOn(AndroidSchedulers.mainThread()) .subscribeOn(Schedulers.io()) .subscribe(result -> { view.hideProgress(); view.onDataReady(result); }, err -> { view.hideProgress(); view.handleError(err); }) ); }}
«Do» operators in actionpublic class HomeScreenPresenter { final HomeView view; final HomeInteractor interactor = new HomeInteractorImpl();
public HomeScreenPresenter(HomeView view){ this.view = view; }
public void getClientData(String token) { view.manageSubscription(interactor.loadClientData(token) .observeOn(AndroidSchedulers.mainThread()) .subscribeOn(Schedulers.io()) .doOnSubscribe(view::showProgress) .doOnTerminate(view::hideProgress) .subscribe(result -> { view.onDataReady(result); }, err -> { view.handleError(err);}) ); }}
«Do» operators in actionpublic class HomeScreenPresenter { final HomeView view; final HomeInteractor interactor = new HomeInteractorImpl();
public HomeScreenPresenter(HomeView view){ this.view = view; }
public void getClientData(String token) { view.manageSubscription(interactor.loadClientData(token) .observeOn(AndroidSchedulers.mainThread()) .subscribeOn(Schedulers.io()) .compose(RxUtils.progressBarVisibility(view)) .subscribe(result -> { view.onDataReady(result); }, err -> { view.handleError(err);}) ); }}
«Do» operators in actionpublic class HomeScreenPresenter { final HomeView view; final HomeInteractor interactor = new HomeInteractorImpl();
public HomeScreenPresenter(HomeView view){ this.view = view; }
public void getClientData(String token) { view.manageSubscription(interactor.loadClientData(token) .observeOn(AndroidSchedulers.mainThread()) .subscribeOn(Schedulers.io()) .compose(RxUtils.progressBarVisibility(view)) .subscribe(result -> { view.onDataReady(result); }, err -> { view.handleError(err);}) ); }}
public static <T> Observable.Transformer<T, T> progressBarVisibility(HomeView view) { return targetObservable -> targetObservable.doOnSubscribe(view::showProgress) .doOnTerminate(view::hideProgress);}
Validationfinal Observable<Boolean> nameObservable = RxTextView.textChanges(edtPassword) .skip(1) .map(text -> /* validateInput with side effect */);
final Observable<Boolean> emailObservable = RxTextView.textChanges(edtEmail) .skip(1) .map(text -> /* validateInput with side effect */);
final Observable<Boolean> passwordObservable = RxTextView.textChanges(edtPassword) .skip(1) .map(text -> /* validateInput with side effect */);
final Subscription combinedSub = Observable
.combineLatest(nameObservable, emailObservable, passwordObservable, (name, mail, pass) -> name && mail && pass) .distinctUntilChanged() .subscribe(btnProceed::setEnabled);
compositeSubscription.add(combinedSub);
Validationfinal Observable<Boolean> nameObservable = RxTextView.textChanges(edtPassword) .skip(1) .map(text -> /* validateInput with side effect */);
final Observable<Boolean> emailObservable = RxTextView.textChanges(edtEmail) .skip(1) .map(text -> /* validateInput with side effect */);
final Observable<Boolean> passwordObservable = RxTextView.textChanges(edtPassword) .skip(1) .map(text -> /* validateInput with side effect */);
final Subscription combinedSub = Observable
.combineLatest(nameObservable, emailObservable, passwordObservable, (name, mail, pass) -> name && mail && pass) .distinctUntilChanged() .subscribe(btnProceed::setEnabled);
compositeSubscription.add(combinedSub);
Validationfinal Observable<Boolean> nameObservable = RxTextView.textChanges(edtPassword) .skip(1) .map(text -> /* validateInput with side effect */);
final Observable<Boolean> emailObservable = RxTextView.textChanges(edtEmail) .skip(1) .map(text -> /* validateInput with side effect */);
final Observable<Boolean> passwordObservable = RxTextView.textChanges(edtPassword) .skip(1) .map(text -> /* validateInput with side effect */);
final Subscription combinedSub = Observable
.combineLatest(nameObservable, emailObservable, passwordObservable, (name, mail, pass) -> name && mail && pass) .distinctUntilChanged() .subscribe(btnProceed::setEnabled);
compositeSubscription.add(combinedSub);
«Live» searchRxTextView.textChanges(searchEditText) .flatMap(ApiService::searchItems) .observeOn(AndroidSchedulers.mainThread()) .subscribe(this::updateList, t-> showError());
https://lorentzos.com/improving-ux-with-rxjava-4440a13b157f#.19yawyfss
«Live» searchRxTextView.textChanges(searchEditText) .debounce(205, TimeUnit.MILLISECONDS) .flatMap(ApiService::searchItems) .observeOn(AndroidSchedulers.mainThread()) .subscribe(this::updateList, t-> showError());
https://lorentzos.com/improving-ux-with-rxjava-4440a13b157f#.19yawyfss
«Live» searchRxTextView.textChanges(searchEditText) .debounce(205, TimeUnit.MILLISECONDS) .switchMap(ApiService::searchItems) .observeOn(AndroidSchedulers.mainThread()) .subscribe(this::updateList, t-> showError());
https://lorentzos.com/improving-ux-with-rxjava-4440a13b157f#.19yawyfss
«Live» searchRxTextView.textChanges(searchEditText) .debounce(205, TimeUnit.MILLISECONDS) .switchMap(ApiService::searchItems) .onErrorResumeNext(err -> Observable.empty()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(this::updateList, t-> showError());
https://lorentzos.com/improving-ux-with-rxjava-4440a13b157f#.19yawyfss
«Live» searchRxTextView.textChanges(searchEditText) .debounce(205, TimeUnit.MILLISECONDS) .switchMap(ApiService::searchItems) .retryWhen(new NetworkConnectivity()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(this::updateList, t-> showError());
https://lorentzos.com/improving-ux-with-rxjava-4440a13b157f#.19yawyfss
«Live» searchRxTextView.textChanges(searchEditText) .debounce(205, TimeUnit.MILLISECONDS) .switchMap(ApiService::searchItems) .retryWhen(new NetworkConnectivityIncremental(context, 5, 15, SECONDS)) .observeOn(AndroidSchedulers.mainThread()) .subscribe(this::updateList, t-> showError());
https://lorentzos.com/improving-ux-with-rxjava-4440a13b157f#.19yawyfss
Easy concurrencyNetApiService.loadDocument(docId) .subscribeOn(Schedulers.io()) .observeOn(Schedulers.computation()) // want find matches or smth ASAP .map(data -> MatchFinder.find(data, PATTERN)) // work done, can swith on low priority 'IO' pool .observeOn(Schedulers.io()) .doOnNext(processedData -> DataProvider.saveProcessedDocument(docId, processedData)) .observeOn(AndroidSchedulers.mainThread()) .subscribe(/* do something with result */);
https://twitter.com/mr_art_Core