Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Reactive Programming on Android with RxJava

Reactive Programming on Android with RxJava

An introduction to Reactive Programming on Android using RxJava and RxAndroid

Faiçal Tchirou

November 25, 2016
Tweet

More Decks by Faiçal Tchirou

Other Decks in Programming

Transcript

  1. Why Reactive Programming? Traditional programming paradigms are showing their limits

    when it comes to building modern web and mobile apps.
  2. Without Reactive Programming Github API 1 public interface GithubAPI {

    2 3 void search(String query, Callback callback); 4 5 void loadNextPage(Callback callback); 6 7 void cancel(); 8 9 interface Callback { 10 11 void onSuccess(List<GithubRepository> repositories); 12 13 void onError(GithubError error); 14 15 void onLimitExceeded(); 16 17 void onCancelled(); 18 19 void onFailure(Throwable t); 20 } 21 }
  3. Without Reactive Programming Listening to text changes on the search

    edit text 1 searchEditText.addTextChangedListener(new TextWatcher() { 2 @Override 3 public void afterTextChanged(final Editable s) { 4 if (query == null || query.trim().isEmpty()) { 5 header.setText(R.string.no_repository_found); 6 adapter.setRepositories(new ArrayList<GithubRepository>()); 7 return; 8 } 9 10 header.setText(R.string.searching); 11 searchProgressBar.setVisibility(View.VISIBLE); 12 recyclerView.setVisibility(View.INVISIBLE); 13 14 throttleSearchRequest(s.toString()); 15 } 16 });
  4. Without Reactive Programming Throttling and sending the search request 1

    private void throttleSearchRequest(String query) { 2 this.searchQuery = query; 3 4 cancelThrottledSearchRequests(); 5 6 throttler.schedule(new TimerTask() { 7 @Override 8 public void run() { 9 if (searchQuery.trim().isEmpty()) { 10 return; 11 } 12 13 callback.isSearching = true; 14 callback.isLoadingNextPage = false; 15 16 githubAPI.search(searchQuery, callback); 17 } 18 }, 300); 19 }
  5. Without Reactive Programming Github API Callback 1 class GithubSearchServiceCallback implements

    GithubSearchService.Callback { 2 3 private boolean isSearching; 4 private boolean isLoadingNextPage; 5 6 @Override 7 public void onSuccess(List<GithubRepository> repositories) { 8 if (isSearching) { 9 adapter.setRepositories(repositories); 10 } else if (isLoadingNextPage) { 11 adapter.addRepositories(repositories); 12 } 13 14 updateUI(); 15 } 16 17 // ...
  6. Without Reactive Programming Github API Callback 1 private void updateUI()

    { 2 if (isSearching) { 3 searchProgressBar.setVisibility(View.INVISIBLE); 4 } else if (isLoadingNextPage) { 5 loadNextPageProgressBar.setVisibility(View.GONE); 6 } 7 8 int count = adapter.getItemCount(); 9 10 header.setText(count == 0 11 ? getResources().getString(R.string.no_repository_found) 12 : getResources().getString(R.string.repositories_found, count)); 13 14 recyclerView.setVisibility(View.VISIBLE); 15 }
  7. Without Reactive Programming Github API Callback 17 // ... 18

    19 @Override 20 public void onError(GithubError error) { 21 // ... 22 } 23 24 @Override 25 public void onLimitExceeded() { 26 // ... 27 displayRateLimitMessageAndWaitForDelay(); 28 } 29 21 // ...
  8. Without Reactive Programming Handling the rate limit 1 private void

    displayRateLimitMessageAndWaitForDelay() { 2 searchEditText.setEnabled(false); 3 recyclerView.setLayoutFrozen(true); 4 5 final Snackbar snackbar = Snackbar.make(root, 6 getResources().getString(R.string.rate_limit_exceeded, 60), 7 Snackbar.LENGTH_INDEFINITE); 8 9 snackbar.show(); 10 11 // ...
  9. Without Reactive Programming Rate Limit Countdown 11 // ... 12

    13 final Timer timer = new Timer(); 14 int seconds = 60; 15 16 timer.scheduleAtFixedRate(() -> { 17 seconds--; 18 runOnUiThread(() -> { 19 if (seconds <= 0) { 20 snackbar.dismiss(); 21 timer.cancel(); 22 23 searchEditText.setEnabled(true); 24 recyclerView.setLayoutFrozen(false); 25 return; 26 } 27 snackbar.setText(…getString(R.string.rate_limit_exceeded, seconds); 28 }); 29 }, 1000, 1000); 30 }
  10. Without Reactive Programming Pagination 1 repositoriesView.addOnScrollListener(new RecyclerView.OnScrollListener() { 2 @Override

    3 public void onScrolled(RecyclerView recyclerView, int dx, int dy) { 4 super.onScrolled(recyclerView, dx, dy); 5 6 int lastItemPosition = layoutManager.findLastCompletelyVisibleItemPosition(); 7 8 if (isLastVisibileRepository(lastItemPosition)) { 9 loadNextPageProgressBar.setVisibility(View.VISIBLE); 10 11 callback.isLoadingNextPage = true; 12 callback.isSearching = false; 13 14 githubAPI.loadNextPage(callback); 15 } 16 } 17 });
  11. Principles of Reactive Programming 1. Everything is a sequence* 2.

    A sequence can be transformed into or combined with another sequence using reactive operators.
  12. Principles of Reactive Programming 1. Everything is a sequence* 2.

    A sequence can be transformed into or be combined with another sequence using reactive operators. 3. A sequence can be observed or subscribed to and the observers can run some action when the sequence is updated.
  13. What is RxJava? Reactive Programming is a programming pattern. RxJava

    is the implementation in Java of that pattern.
  14. What is RxJava? RxJava is a Java library that •

    Provides an abstraction of the concept of sequence named Observable.
  15. What is RxJava? RxJava is a Java library that •

    Provides an abstraction of the concept of sequence named Observable. • Provides an implementation of reactive operators used to transform and combine Observables.
  16. What is RxJava? RxJava is a Java library that •

    Provides an abstraction of the concept of sequence named Observable. • Provides an implementation of reactive operators used to transform and combine Observables. • Provides an abstraction for objects which can subscribe to Observables named Observer.
  17. Creating Observables 1 Observable.create((subscriber) -> { 2 if (!subscriber.isUnsubscribed()) {

    3 for (int i = 1; i <= 10; ++i) { 4 subscriber.onNext(i); 5 } 6 7 subscriber.onCompleted(); 8 } 9 }); 10 11 // => { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 } create
  18. Creating Observables 1 Integer[] items = new Integer[] { 1,

    2, 3, 4, 5 }; 2 Observable observable = Observable.from(items); 3 4 // => { 1, 2, 3, 4, 5 } from
  19. Transforming Observables 1 Observable 2 .from(new Integer[] { 1, 2,

    3, 4, 5 }) 3 .map((integer) -> return integer * integer); 4 5 // => { 1, 4, 9, 16, 25 } map
  20. Transforming Observables 1 Observable 2 .from(new Integer[] { 1, 2,

    3, 4, 5 }) 3 .flatMap(integer -> return Observable.just(integer).repeat(integer)); 4 5 // => { 1, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5 } flatMap
  21. Filtering Observables 1 Observable 2 .from(new Integer[] { 1, 2,

    3, 4, 5 }) 3 .filter(integer -> return integer % 2 == 0) 4 5 // => { 2, 4 } filter
  22. Filtering Observables 1 Observable 2 .from(new Integer[] { 1, 2,

    3, 2, 1, 3, 4, 5 }) 3 .distinct() 4 5 // => { 1, 2, 3, 4, 5 } distinct
  23. Combining Observables 1 Observable odds = Observable.from(new Integer[] { 1,

    3 }; 2 Observable evens = Observable.from(new Integer[] { 2, 4 }; 3 Observable.merge(odds, evens); 4 5 // => { 1, 2, 3, 4 } merge
  24. Combining Observables 1 Observable strings = Observable.from(new String[] { “a”,

    “b” }; 2 Observable integers = Observable.from(new Integer[] { 1, 2, 3 }; 3 Observable.combineLatest(strings, integers, 4 (string, integer) -> return new Pair(string, integer)); 5 6 // => { (“a”, 1), (“b”, 2), (“b”, 3) } combineLatest
  25. Combining Observables 1 Observable strings = Observable.from(new String[] { “a”,

    “b” }; 2 Observable integers = Observable.from(new Integer[] { 1, 2, 3 }; 3 Observable.zip(strings, integers, 4 (string, integer) -> return new Pair(string, integer)); 5 6 // => { (“a”, 1), (“b”, 2) } zip
  26. Subscribing to an Observable 1 Observable.from(new Integer[] { 1, 2,

    3, 4, 5 }) 2 .subscribe(new Observer<Integer> { 3 @Override 4 public void onNext(Integer integer) { 5 System.out.println("Emitted " + integer); 6 } 7 8 @Override 9 public void onError(Throwable t) { 10 System.out.println(t); 11 } 12 13 @Override 14 public void onCompleted() { 15 System.out.println("Completed!"); 16 } 17 }); Emitted 1 Emitted 2 Emitted 3 Emitted 4 Emitted 5 Completed!
  27. Subscribing to an Observable 1 Observable.from(new Integer[] { 1, 2,

    3, 4, 5 }) 2 .subscribe( 3 (integer) -> { 5 System.out.println("Emitted " + integer); 6 }, 7 (error) -> { 8 System.out.println(error); 9 }, 10 () -> { 11 System.out.println(“Completed!”); 12 } 13 ); Emitted 1 Emitted 2 Emitted 3 Emitted 4 Emitted 5 Completed!
  28. Rx-ifying Github Search Github API 1 import rx.Observable; 2 3

    public interface GithubAPI { 4 5 Observable<List<GithubRepository>> search(String query); 6 7 Observable<List<GithubRepository>> loadNextPage(); 8 9 Observable<Boolean> cancel(); 10 }
  29. Rx-ifying Github Search ViewModel 1 public class ViewModel { 2

    3 private GithubAPI githubAPI; 4 5 Subject<String> searchQuery; 6 7 Subject<Boolean> searchRequested; 8 9 Subject<Boolean> nextPageRequested; 10 11 Subject<List<GithubRepository>> repositories; 12 13 Subject<Boolean> repositoriesFetched; 14 15 Subject<Boolean> rateLimitExceeded; 16 17 // ... 18 }
  30. Rx-ifying Github Search Searching 1 public class ViewModel { 2

    3 // ... 4 5 private void search(String query) { 6 githubAPI 7 .search(query) 8 .subscribe( 9 (repos) -> { 10 repositories.onNext(repos); 11 }, 12 (error) -> { 13 if (error instanceof GithubRateLimitError) { 14 rateLimitExceeded.onNext(true); 15 } 16 } 17 ); 18 } 19 20 // … 21 }
  31. Rx-ifying Github Search Loading the next page 1 public class

    ViewModel { 2 3 // ... 4 5 private void loadNextPage() { 6 githubAPI 7 .loadNextPage() 8 .subscribe( 9 (repos) -> { 10 repositories.onNext(repos); 11 }, 12 (error) -> { 13 if (error instanceof GithubRateLimitError) { 14 rateLimitExceeded.onNext(true); 15 } 16 } 17 ); 18 } 19 20 // … 21 }
  32. Rx-ifying Github Search Setting up the ViewModel subscriptions 1 public

    class ViewModel { 2 3 // ... 4 5 private void setupSubscriptions() { 6 searchQuery.subscribe((query) -> { search(query); }); 7 8 nextPageRequested.subscribe((requested) -> { loadNextPage(); }); 9 10 repositories.subscribe((repos) -> { repositoriesFetched.onNext(true); } ); 11 } 12 13 // ... 14 }
  33. Rx-ifying Github Search MainActivity - Search 1 public class MainActivity

    extends AppCompatActivity { 2 3 ViewModel viewModel; 4 5 private void setupSubscriptions() { 6 RxTextView.afterTextChangeEvents(searchEditText) 7 .map((event) -> { return true; }) 8 .subscribe(viewModel.searchRequested); 9 10 RxTextView.afterTextChangeEvents(searchEditText) 11 .debounce(300, TimeUnit.MILLISECONDS) 12 .map((event) -> { return event.editable().toString(); }) 13 .subscribe(viewModel.searchQuery); 14 15 viewModel.searchRequested 16 .observeOn(AndroidSchedulers.mainThread()) 17 .subscribe(RxView.visibility(searchProgressBar)); 18 19 // ...
  34. Rx-ifying Github Search MainActivity - Updating the UI 1 public

    class MainActivity extends AppCompatActivity { 2 3 ViewModel viewModel; 4 5 private void setupSubscriptions() { 6 // ... 7 8 viewModel.repositories 9 .observeOn(AndroidSchedulers.mainThread()) 10 .subscribe((repositories) -> { adapter.addRepositories(repositories); }); 12 13 viewModel.repositoriesFetched 14 .observeOn(AndroidSchedulers.mainThread()) 15 .subscribe(RxView.visibility(repositoriesView)); 16 17 viewModel.repositoriesFetched 18 .observeOn(AndroidSchedulers.mainThread()) 19 .map((fetched) -> { return !fetched; }) 20 .subscribe(RxView.visibility(searchProgressBar)); 21 22 // ... 23 }
  35. Rx-ifying Github Search MainActivity - Pagination 1 public class MainActivity

    extends AppCompatActivity { 2 3 ViewModel viewModel; 4 5 private void setupSubscriptions() { 6 // ... 7 8 RxRecyclerView.scrollEvents(repositoriesView) 9 .map((event) -> { 10 int lastItemPosition = lastCompletelyVisibleItemPosition(); 11 12 return isLastVisibleRepository(lastItemPosition); 13 } 14 }) 15 .subscribe(viewModel.nextPageRequested); 16 17 // … 18 }
  36. Rx-ifying Github Search MainActivity - Handling the rate limit 1

    public class MainActivity extends AppCompatActivity { 2 3 ViewModel viewModel; 4 5 private void setupSubscriptions() { 6 // ... 7 8 viewModel.rateLimitExceeded 9 .subscribe(RxSnackbar.visible(snackbar)); 10 11 viewModel.rateLimitExceeded 12 .subscribe(RxEditText.disabled(searchEditText)); 13 14 viewModel.rateLimitExceeded 15 .subscribe(RxRecyclerView.disabled(repositoriesView)); 16 17 // … 18 }
  37. Rx-ifying Github Search MainActivity - Rate Limit Countdown 1 public

    class MainActivity extends AppCompatActivity { 2 3 private void setupSubscriptions() { 4 viewModel.rateLimitExceeded 5 .filter((exceeded) -> { return exceeded; }) 6 .flatMap((exceeded) -> { 7 return Observable.zip( 8 Observable.range(0, 60), 9 Observable.interval(1, TimeUnit.SECONDS), 10 (count) -> { return 59L - count; } 11 ) 12 .doOnNext((seconds) -> { 13 if (seconds == 0) { 14 viewModel.rateLimitExceeded.onNext(false); 15 } 16 }) 17 }) 18 .map((seconds) -> { return ….getString(R.string.rate_limit_exceeded, seconds); } 19 .observeOn(AndroidSchedulers.mainThread()) 20 .subscribe(RxSnackbar.text(snackbar)); 21 }