Slide 1

Slide 1 text

RxJava 介紹與 Android 中的 RxJava ⿈黃千碩 (Kros) oSolve Ltd. / Wish8 Co,. Ltd. Mobile App Developer

Slide 2

Slide 2 text

Outline • RxJava Introduction • Observable • Operators • Subject • Scheduler • Android Lifecycle • Testing • Performance

Slide 3

Slide 3 text

What is RxJava? • RxJava 是 Reactive X (Reactive Extensions) 對 Java VM 的實作 • 是⼀一個 Library,利⽤用資料流 (observable sequences) 來處理 「asynchronous 與 event- base 」類型的程式。 • 主要⾓角⾊色為:observable 與 observer 。

Slide 4

Slide 4 text

What is Reactive? • 翻譯:「響應式」「反應式」開發 • 把資料 (data) 或是事件 (event) 變成「可觀察」 (observer pattern) 的「資料流」。 • 並加上運算元 (operators) 來操作這些資料。

Slide 5

Slide 5 text

What is FRP ? • FRP - Functional Reactive Programming. • Reactive 是⺫⽬目的 • 為了能讓開發者不落⼊入如何處理(事件)資料的 繁雜程式邏輯中,利⽤用 函數式 (Functional) 的⽅方 法來處理資料流 • filter(), map(), flatMap(), …etc.

Slide 6

Slide 6 text

Why FRP? • Concurrency • thread 的控管複雜 • Asynchronous Programming • 為追求 60fps,許多事情我們會丟到背景處理 • Callback Hell • 當 Callback 太多時,眼睛都花了。

Slide 7

Slide 7 text

– 林信良 “若開發者是以務實且不斷提升作為⾃自我期許,更 ⾼高階的抽象化作法將會是必修的課題”

Slide 8

Slide 8 text

Observable • Observable 為發射資料的⼈人

Slide 9

Slide 9 text

Create Observable • Observable.just() • Observable.from() • …etc.

Slide 10

Slide 10 text

Observable.just() • 把「資料」轉變成 Observable。 Observable.just("Hello World!")

Slide 11

Slide 11 text

Observer • Observer 為對這些資料有興趣的⼈人 • 透過 subscribe method 連結 observer 與 observable. • Observer 透過 subscribe 來監聽⼀一個 Observable.

Slide 12

Slide 12 text

Subscribe • 連結 observable 與 observer • 通常必須實作 subscribe 的 interface. • onNext, onError, onComplete public final Subscription subscribe(final Action1 onNext, final Action1 onError, final Action0 onComplete) { /* ... */ }

Slide 13

Slide 13 text

>>>>>>>>>>>>>>>>>>> s:Hello World! Observable.just("Hello World!").subscribe(new Action1() {
 @Override
 public void call(String s) {
 System.out.println(">>>>>>>>>>>>>>>>>>> s:" + s);
 }
 }, new Action1() {
 @Override
 public void call(Throwable throwable) {
 }
 }, new Action0() {
 @Override
 public void call() {
 }
 });

Slide 14

Slide 14 text

>>>>>>>>>>>>>>>>>>> s:Hello World! Observable.just("Hello World!").subscribe(new Action1() {
 @Override
 public void call(String s) {
 System.out.println(">>>>>>>>>>>>>>>>>>> s:" + s);
 }
 }, new Action1() {
 @Override
 public void call(Throwable throwable) {
 }
 }, new Action0() {
 @Override
 public void call() {
 }
 }); Observable.just("Hello World!").subscribe(new Action1() {
 @Override
 public void call(String s) {
 System.out.println(">>>>>>>>>>>>>>>>>>> s:" + s);
 }
 }); 可以只實作感興趣的 callback

Slide 15

Slide 15 text

>>>>>>>>>>>>>>>>>>> s:Hello World! Observable.just("Hello World!").subscribe(new Action1() {
 @Override
 public void call(String s) {
 System.out.println(">>>>>>>>>>>>>>>>>>> s:" + s);
 }
 }, new Action1() {
 @Override
 public void call(Throwable throwable) {
 }
 }, new Action0() {
 @Override
 public void call() {
 }
 }); Observable.just("Hello World!").subscribe(new Action1() {
 @Override
 public void call(String s) {
 System.out.println(">>>>>>>>>>>>>>>>>>> s:" + s);
 }
 }); Observable.just("Hello World!").subscribe(s -> {
 System.out.println(">>>>>>>>>>>>>>>>>>> s:" + s);
 }); 套⽤用 retrolambda,採⽤用 java8 lambda,讓程式碼更簡潔

Slide 16

Slide 16 text

Observable.from • 把「⼀一包資料」轉變成 Observable。⽽而這個 Observable 每次只發射資料中的單⼀一資料 Observable.from(listOfIntegers)

Slide 17

Slide 17 text

Observable.from >>>>>>>>>>>>>>>>>>> integer:1 >>>>>>>>>>>>>>>>>>> integer:2 >>>>>>>>>>>>>>>>>>> integer:3 >>>>>>>>>>>>>>>>>>> integer:4 >>>>>>>>>>>>>>>>>>> integer:5 >>>>>>>>>>>>>>>>>>> integer:6 >>>>>>>>>>>>>>>>>>> integer:7 List integers = new ArrayList<>();
 integers.add(1);
 // ...
 integers.add(7); 
 Observable.from(integers).subscribe(integer -> {
 System.out.println(">>>>>>>>>>>>>>>>>>> integer:" + integer);
 });

Slide 18

Slide 18 text

“Hot” and “Cold” Observable • Observable 什麼時候會發射資料呢? • Hot observable • 當它⼀一建⽴立時就會發射資料 • Cold observable • 當有 observer subscribe 時,才會發射資料

Slide 19

Slide 19 text

Operators • Creating Observables (ex: create, from, just, …) • Transforming Observables (ex: map, flatMap, …) • Filtering Observables • Combining Observables • Error Handling Operators • Observable Utility Operators • ……etc.

Slide 20

Slide 20 text

Observable.just("Hello World!").map(s -> s + " Android Taipei")
 .subscribe(s -> {
 System.out.println(">>>>>>>>>>>>>>>>>>> s:" + s);
 }); Observable.from(integers)
 .map(integer -> integer + 10)
 .subscribe(integer -> {
 System.out.println(">>>>>>>>>>>>>>>>>>> integer:" + integer);
 }); >>>>>>>>>>>>>>>>>>> s:Hello World! Android Taipei >>>>>>>>>>>>>>>>>>> integer:11 >>>>>>>>>>>>>>>>>>> integer:12 >>>>>>>>>>>>>>>>>>> integer:13 >>>>>>>>>>>>>>>>>>> integer:14 >>>>>>>>>>>>>>>>>>> integer:15 >>>>>>>>>>>>>>>>>>> integer:16 >>>>>>>>>>>>>>>>>>> integer:17 對 "Hello World!" 加⼯工 對 list 中每個 element 加⼯工

Slide 21

Slide 21 text

台北市 公廁查詢系統 利⽤用台北市政府 Open Data http://data.taipei/ ⺫⽬目的:找尋離我最近的公廁

Slide 22

Slide 22 text

http://data.taipei/

Slide 23

Slide 23 text

Callback 版本

Slide 24

Slide 24 text

// - 跟 Server 抓取公廁資料 @GET("/apiAccess")
 void listToiletCallback(@Query("rid") String rid, 
 @Query("scope") String scope,
 @Query("limit") int limit,
 @Query("offset") int offset,
 Callback callback);

Slide 25

Slide 25 text

// - 跟 Server 抓取公廁資料 @GET("/apiAccess")
 void listToiletCallback(@Query("rid") String rid, 
 @Query("scope") String scope,
 @Query("limit") int limit,
 @Query("offset") int offset,
 Callback callback); @Override
 public void onViewCreated(View view, Bundle savedInstanceState) {
 super.onViewCreated(view, savedInstanceState);
 progressBar.setVisibility(View.VISIBLE); fetchNearestToilet(); }

Slide 26

Slide 26 text

// - 跟 Server 抓取公廁資料 @GET("/apiAccess")
 void listToiletCallback(@Query("rid") String rid, 
 @Query("scope") String scope,
 @Query("limit") int limit,
 @Query("offset") int offset,
 Callback callback); @Override
 public void onViewCreated(View view, Bundle savedInstanceState) {
 super.onViewCreated(view, savedInstanceState);
 progressBar.setVisibility(View.VISIBLE); fetchNearestToilet(); } ⺫⽬目的:找出距離我 5 km 以內的公廁, 並按照遠近排序

Slide 27

Slide 27 text

private void fetchNearestToilet() {
 apiService.listToiletCallback(RID, SCOPE, 500, 0, new Callback() {
 @Override
 public void success(ApiResponse apiResponse, Response response) {
 List filtered = new ArrayList<>();
 for (Toilet toilet : apiResponse.getResult().getToilets()) {
 if (lessThan5Km(toilet)) {
 filtered.add(toilet);
 }
 }
 Collections.sort(filtered, new Comparator() {
 @Override
 public int compare(Toilet lhs, Toilet rhs) {
 return compareDistance(lhs, rhs);
 }
 });
 adapter.reset(filtered);
 progressBar.setVisibility(View.GONE);
 }
 
 @Override
 public void failure(RetrofitError error) {
 progressBar.setVisibility(View.GONE);
 ViewHelper.showError(getActivity(), error);
 }
 });
 }

Slide 28

Slide 28 text

RxJava 版本

Slide 29

Slide 29 text

// - 跟 Server 抓取公廁資料 @GET("/apiAccess")
 Observable listToilet(@Query("rid") String rid, 
 @Query("scope") String scope, 
 @Query("limit") int limit, 
 @Query("offset") int offset);

Slide 30

Slide 30 text

// - 跟 Server 抓取公廁資料 @GET("/apiAccess")
 Observable listToilet(@Query("rid") String rid, 
 @Query("scope") String scope, 
 @Query("limit") int limit, 
 @Query("offset") int offset); @Override
 public void onViewCreated(View view, Bundle savedInstanceState) {
 super.onViewCreated(view, savedInstanceState);
 progressBar.setVisibility(View.VISIBLE);
 fetchNearestToilet()
 .observeOn(AndroidSchedulers.mainThread())
 .finallyDo(() -> progressBar.setVisibility(View.GONE))
 .subscribe((toilets) -> {
 adapter.reset(toilets);
 },
 throwable -> ViewHelper.showError(getActivity(), throwable)); } }

Slide 31

Slide 31 text

// - 跟 Server 抓取公廁資料 @GET("/apiAccess")
 Observable listToilet(@Query("rid") String rid, 
 @Query("scope") String scope, 
 @Query("limit") int limit, 
 @Query("offset") int offset); @Override
 public void onViewCreated(View view, Bundle savedInstanceState) {
 super.onViewCreated(view, savedInstanceState);
 progressBar.setVisibility(View.VISIBLE);
 fetchNearestToilet()
 .observeOn(AndroidSchedulers.mainThread())
 .finallyDo(() -> progressBar.setVisibility(View.GONE))
 .subscribe((toilets) -> {
 adapter.reset(toilets);
 },
 throwable -> ViewHelper.showError(getActivity(), throwable)); } }

Slide 32

Slide 32 text

// - 跟 Server 抓取公廁資料 @GET("/apiAccess")
 Observable listToilet(@Query("rid") String rid, 
 @Query("scope") String scope, 
 @Query("limit") int limit, 
 @Query("offset") int offset); @Override
 public void onViewCreated(View view, Bundle savedInstanceState) {
 super.onViewCreated(view, savedInstanceState);
 progressBar.setVisibility(View.VISIBLE);
 fetchNearestToilet()
 .observeOn(AndroidSchedulers.mainThread())
 .finallyDo(() -> progressBar.setVisibility(View.GONE))
 .subscribe(adapter::reset,
 throwable -> ViewHelper.showError(getActivity(), throwable)); } Java 8 的 method reference

Slide 33

Slide 33 text

// - 跟 Server 抓取公廁資料 @GET("/apiAccess")
 Observable listToilet(@Query("rid") String rid, 
 @Query("scope") String scope, 
 @Query("limit") int limit, 
 @Query("offset") int offset); @Override
 public void onViewCreated(View view, Bundle savedInstanceState) {
 super.onViewCreated(view, savedInstanceState);
 progressBar.setVisibility(View.VISIBLE);
 fetchNearestToilet()
 .observeOn(AndroidSchedulers.mainThread())
 .finallyDo(() -> progressBar.setVisibility(View.GONE))
 .subscribe(adapter::reset,
 throwable -> ViewHelper.showError(getActivity(), throwable)); }

Slide 34

Slide 34 text

private Observable> fetchNearestToilet() {
 return apiService.listToilet(RID, SCOPE, 500, 0)
 .flatMap(response -> Observable.from(response.getResult().getToilets()))
 .filter(this::lessThan5Km)
 .toSortedList(this::compareDistance);
 } // - 跟 Server 抓取公廁資料 @GET("/apiAccess")
 Observable listToilet(@Query("rid") String rid, 
 @Query("scope") String scope, 
 @Query("limit") int limit, 
 @Query("offset") int offset); @Override
 public void onViewCreated(View view, Bundle savedInstanceState) {
 super.onViewCreated(view, savedInstanceState);
 progressBar.setVisibility(View.VISIBLE);
 fetchNearestToilet()
 .observeOn(AndroidSchedulers.mainThread())
 .finallyDo(() -> progressBar.setVisibility(View.GONE))
 .subscribe(adapter::reset,
 throwable -> ViewHelper.showError(getActivity(), throwable));
 } }

Slide 35

Slide 35 text

No content

Slide 36

Slide 36 text

Subject • 翻譯:主題 • A Subject is a sort of bridge or proxy that is available in some implementations of ReactiveX that acts both as an observer and as an Observable. • Subject 可以是發送 event 的⼈人 (observable), 也可以是註冊 event 的⼈人 (observer)。 • ⽤用途:Event Bus

Slide 37

Slide 37 text

Subject • Subject 有很多種: • AsyncSubject • BehaviorSubject • PublishSubject • ReplaySubject

Slide 38

Slide 38 text

Publish Subject • 會發送給每個 observers • 只會接收到 subscribe 之後的 event

Slide 39

Slide 39 text

Subject

Slide 40

Slide 40 text

Activity 1 Subject

Slide 41

Slide 41 text

Activity 1 Subject subject.subscribe();

Slide 42

Slide 42 text

Activity 1 Subject startActivity(); subject.subscribe();

Slide 43

Slide 43 text

Activity 1 Activity 2 Subject startActivity(); subject.subscribe();

Slide 44

Slide 44 text

Activity 1 Activity 2 Subject startActivity(); // Do something… subject.onNext(Event); finish(); subject.subscribe();

Slide 45

Slide 45 text

Activity 1 Activity 2 Subject startActivity(); subject.subscribe(); // Do something… subject.onNext(Event); finish();

Slide 46

Slide 46 text

Activity 1 Activity 2 Subject startActivity(); subject.subscribe(); // Do something… subject.onNext(Event); finish();

Slide 47

Slide 47 text

Activity 1 Activity 2 Subject startActivity(); subject.subscribe(); // Do something… subject.onNext(Event); finish();

Slide 48

Slide 48 text

Activity 1 Activity 2 Subject startActivity(); subject.subscribe(); // Do something… subject.onNext(Event); finish();

Slide 49

Slide 49 text

Activity 2 Subject startActivity(); subject.subscribe(); // Do something… subject.onNext(Event); finish(); Activity 1 (with Event)

Slide 50

Slide 50 text

Activity 1 (with Event) Activity 2 Subject startActivity(); subject.subscribe(); // Do something… subject.onNext(Event); finish();

Slide 51

Slide 51 text

Subject subject.subscribe(); Activity 1 (with Event)

Slide 52

Slide 52 text

Subject subject.subscribe(); Activity 1 (with Event) Activity 2 (with Event) Activity 3 (with Event) subject.subscribe(); subject.subscribe(); 可以很多⼈人註冊

Slide 53

Slide 53 text

Scheduler • If you want to introduce multithreading into your cascade of Observable operators, you can do so by instructing those operators (or particular Observables) to operate on particular Schedulers. • 可以利⽤用 Scheduler 來實作 thread 的切換。

Slide 54

Slide 54 text

Scheduler @Override
 public void onViewCreated(View view, Bundle savedInstanceState) {
 super.onViewCreated(view, savedInstanceState);
 progressBar.setVisibility(View.VISIBLE);
 fetchNearestToilet()
 .observeOn(AndroidSchedulers.mainThread())
 .finallyDo(() -> progressBar.setVisibility(View.GONE))
 .subscribe(adapter::reset,
 throwable -> ViewHelper.showError(getActivity(), throwable));
 } }

Slide 55

Slide 55 text

Android Lifecycle • Activity 與 Fragment 都有各⾃自的 lifecycle. • Activity, onCreate(), onResume(), onPause(), onDestory(), ..etc. • Fragment, onCreate(), onCreateView(), onResume(), onPause(), onDestory(), ..etc • 如果 Activity/Fragment 被 destroy 時,你的 async task 還沒做完怎麼辦?

Slide 56

Slide 56 text

Android Lifecycle • 會導致 Memory leak 或是 NPE. • Activity 與 Fragment 都有各⾃自的 lifecycle. • Activity, onCreate(), onResume(), onPause(), onDestory(), ..etc. • Fragment, onCreate(), onCreateView(), onResume(), onPause(), onDestory(), ..etc • 如果 Activity/Fragment 被 destroy 時,你的 async task 還沒做完怎麼辦?

Slide 57

Slide 57 text

Android Lifecycle • Import RxJava Android 版
 compile 'io.reactivex:rxandroid:0.25.0' • 使⽤用 Android 相關的 observable 與 event.
 rx.android.lifecycle.LifecycleObservable
 rx.android.lifecycle.LifecycleEvent

Slide 58

Slide 58 text

@Override
 protected void onStart() {
 super.onStart();
 lifecycleSubject.onNext(LifecycleEvent.START);
 }
 
 @Override
 protected void onResume() {
 super.onResume();
 lifecycleSubject.onNext(LifecycleEvent.RESUME);
 }
 
 @Override
 protected void onPause() {
 lifecycleSubject.onNext(LifecycleEvent.PAUSE);
 super.onPause();
 }
 
 @Override
 protected void onStop() {
 lifecycleSubject.onNext(LifecycleEvent.STOP);
 super.onStop();
 }
 
 @Override
 protected void onDestroy() {
 lifecycleSubject.onNext(LifecycleEvent.DESTROY);
 super.onDestroy();
 } private final BehaviorSubject lifecycleSubject = BehaviorSubject.create(); public class BaseActivity extends AppCompatActivity { }

Slide 59

Slide 59 text

public class BaseActivity extends AppCompatActivity { /* reset code */ public Observable lifecycle() {
 return lifecycleSubject.asObservable();
 } protected Observable bind(Observable observable) {
 return LifecycleObservable.bindActivityLifecycle(lifecycle(),
 observable.observeOn(AndroidSchedulers.mainThread()));
 } /* reset code */ }

Slide 60

Slide 60 text

@Override
 public void onViewCreated(View view, Bundle savedInstanceState) {
 super.onViewCreated(view, savedInstanceState);
 progressBar.setVisibility(View.VISIBLE);
 bind(fetchNearestToilet())
 .finallyDo(() -> progressBar.setVisibility(View.GONE))
 .subscribe(adapter::reset,
 throwable -> ViewHelper.showError(getActivity(), throwable));
 } } @Override
 public void onViewCreated(View view, Bundle savedInstanceState) {
 super.onViewCreated(view, savedInstanceState);
 progressBar.setVisibility(View.VISIBLE);
 fetchNearestToilet()
 .observeOn(AndroidSchedulers.mainThread())
 .finallyDo(() -> progressBar.setVisibility(View.GONE))
 .subscribe(adapter::reset,
 throwable -> ViewHelper.showError(getActivity(), throwable));
 } } bind() 的功能:當 fragment 被 destroyed 時,會⾃自動 unsubscribe 此 observable.

Slide 61

Slide 61 text

@Override
 public void onViewCreated(View view, Bundle savedInstanceState) {
 super.onViewCreated(view, savedInstanceState);
 progressBar.setVisibility(View.VISIBLE);
 fetchNearestToilet()
 .observeOn(AndroidSchedulers.mainThread())
 .finallyDo(() -> progressBar.setVisibility(View.GONE))
 .subscribe(adapter::reset,
 throwable -> ViewHelper.showError(getActivity(), throwable));
 } } @Override
 public void onViewCreated(View view, Bundle savedInstanceState) {
 super.onViewCreated(view, savedInstanceState);
 progressBar.setVisibility(View.VISIBLE);
 bind(fetchNearestToilet())
 .finallyDo(() -> progressBar.setVisibility(View.GONE))
 .subscribe(adapter::reset,
 throwable -> ViewHelper.showError(getActivity(), throwable));
 } } bind() 的功能:當 fragment 被 destroyed 時,會⾃自動 unsubscribe 此 observable.

Slide 62

Slide 62 text

Testing • 測試容易 • 簡易的⽅方法, toBlocking() • 正規的⽅方法, TestSubscriber()

Slide 63

Slide 63 text

public class AccountDaemon { public Observable login(final Account account) {
 return Observable.just(account).map(account1 -> {
 checkAccount(account);
 return accountService.login(account);
 });
 } private void checkAccount(Account account) throws IllegalArgumentException {
 if (TextUtils.isEmpty(account.getEmail())
 || TextUtils.isEmpty(account.getPassword())) {
 throw new IllegalArgumentException("Email or password can not be empty.");
 }
 } } public class Account {
 private final String email;
 private final String password;
 public static Account createLoginAccount(final String email, 
 final String password) {
 return new Account(email, password);
 }
 // rest implementation… }

Slide 64

Slide 64 text

public void testLogin_empty_email() throws Exception {
 Account account = Account.createLoginAccount(null, "password");
 try {
 accountDaemon.login(account).toBlocking().single();
 fail("method should throw exception");
 } catch (Throwable ex) {
 assertEquals("Email or password can not be empty.", ex.getLocalizedMessage());
 }
 } // Official way public void testLogin_using_test_subscriber() {
 TestSubscriber testSubscriber = new TestSubscriber<>();
 Account account = Account.createLoginAccount("email", "password");
 accountDaemon.login(account).subscribe(testSubscriber);
 
 Account expect = Account.createLoginAccount("email", "password");
 testSubscriber.assertNoErrors();
 testSubscriber.assertValue(expect);
 } // Blocking way public void testLogin() throws Exception {
 Account account = Account.createLoginAccount("email", "password");
 Account result = accountDaemon.login(account).toBlocking().single();
 assertEquals("email", result.getEmail());
 assertEquals("password", result.getPassword());
 }

Slide 65

Slide 65 text

Performance

Slide 66

Slide 66 text

public void testPerformance_rx() {
 List data = new ArrayList<>();
 for (int i = 0; i < 100000; ++i) {
 data.add(i);
 }
 List result = Observable.from(data)
 .filter(integer -> integer % 2 == 0).toList().toBlocking().first();
 assertEquals(100000 / 2, result.size());
 } public void testPerformance_for_loop() {
 List data = new ArrayList<>();
 for (int i = 0; i < 100000; ++i) {
 data.add(i);
 }
 List result = new ArrayList<>();
 for (int i = 0, size = data.size(); i < size; i++) {
 if (i % 2 == 0) {
 result.add(i);
 }
 }
 assertEquals(100000 / 2, result.size());
 }

Slide 67

Slide 67 text

不是每個語⾔言的 Rx 版本效 能都很好,導⼊入時需注意 例如:ReactiveCocoa

Slide 68

Slide 68 text

RAC-ReactiveCocoa - (void)testRACPerformance {
 NSArray *array = [self getTestArray];
 [self measureBlock:^{
 RACSequence *sequence = [array.rac_sequence filter:^BOOL(NSNumber *number) {
 return number.intValue % 2 == 0;
 }];
 NSArray *results = sequence.array;
 XCTAssertEqualObjects(@(100000 / 2), @(results.count));
 }];
 } - (void)testNativePerformance {
 NSArray *array = [self getTestArray];
 [self measureBlock:^{
 NSMutableArray *results = [NSMutableArray array];
 for (int i = 0; i < array.count; ++i) {
 NSNumber *number = array[i];
 if (number.intValue % 2 == 0) {
 [results addObject:number];
 }
 }
 XCTAssertEqualObjects(@(100000 / 2), @(results.count));
 }];
 }

Slide 69

Slide 69 text

優缺點 • 優點 • 程式碼清楚,簡潔 • 容易進⾏行 Asynchronous Programming • 缺點 • 學習成本⾼高(map????, flatMap?????, amb???) • ⼊入侵式的,所有 API 被迫改成 Observable

Slide 70

Slide 70 text

public void fetchUserProfile() {
 // code
 }
 
 public void fetchFriends() {
 // code
 }
 
 public void fetchShippingInfo() {
 // code
 } public Observable fetchUserProfile() {
 // code
 }
 
 public Observable> fetchFriends() {
 // code
 }
 
 public Observable fetchShippingInfo() {
 // code
 }

Slide 71

Slide 71 text

Reference • ReactiveX
 http://reactivex.io/ • FRP與函數式-林信良
 http://www.ithome.com.tw/voice/91328 • RxJava Android Patterns
 http://stablekernel.com/blog/replace-asynctask-asynctaskloader-rx- observable-rxjava-android-patterns/ • Architecting Android…The evolution
 http://fernandocejas.com/2015/07/18/architecting-android-the-evolution/ • Unit Testing RxJava Observables
 https://medium.com/ribot-labs/unit-testing-rxjava-6e9540d4a329 • Demo Project
 https://github.com/ch8908/rxjava-demo