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

Rx

 Rx

Slides from Android Tech Talks #10 meetup at @techspacekrk

Jerzy Chalupski

April 14, 2015
Tweet

More Decks by Jerzy Chalupski

Other Decks in Programming

Transcript

  1. RX

  2. RX?

  3. public interface GitHubService {
 @GET("/users")
 List<User> getUsers();
 
 @GET("/users/{user}/repos")
 List<Repo>

    listRepos(@Path("user") User user);
 }
 
 public class User {
 String getLogin();
 }
 
 public class Repo {
 String getDescription(); User getOwner();
 }
  4. GitHubService service = /* retrofit magic */
 
 for (User

    user : service.getUsers()) {
 for (Repo repo : service.listRepos(user)) {
 if (repo.getDescription().contains("android")) {
 /* send invitation email */
 break;
 }
 }
 }
  5. public interface GitHubService {
 @GET("/users")
 List<User> getUsers();
 
 @GET("/users/{user}")
 User

    getUserDetails(@Path("user") User user);
 
 @GET("/users/{user}/repos")
 List<Repo> listRepos(@Path("user") User user);
 }
 
 public class User {
 String getLogin();
 String getLocation();
 }
 
 public class Repo {
 String getName();
 String getDescription();
 String getLanguage();
 boolean isFork(); User getOwner();
 }
  6. GitHubService service = /* retrofit magic */
 
 for (User

    user : service.getUsers()) {
 for (Repo repo : service.listRepos(user)) {
 boolean isAndroidRepo =
 repo.getDescription().contains("android") ||
 repo.getName().contains("android");
 boolean writtenInJava = repo.getLanguage().equals("Java");
 if (isAndroidRepo && writtenInJava && !repo.isFork()) {
 if (service.getDetails(user).getLocation().equals("Kraków")) { /* send invitation email */
 }
 break;
 }
 }
 }
  7. public interface GitHubService {
 @GET("/users")
 List<User> getUsers();
 
 @GET("/users/{user}")
 User

    getUserDetails(@Path("user") User user);
 
 @GET("/users/{user}/repos")
 List<Repo> listRepos(@Path("user") User user);
 }
 
 public class User {
 String getLogin();
 String getLocation();
 }
 
 public class Repo {
 String getName();
 String getDescription();
 String getLanguage();
 boolean isFork(); User getOwner();
 }
  8. public interface GitHubService {
 @GET("/users")
 void getUsers(Callback<List<User>> users);
 
 @GET("/users/{user}")


    void getUserDetails(@Path("user") User user, Callback<User> …);
 
 @GET("/users/{user}/repos")
 void listRepos(@Path("user") User user, Callback<List<Repo>> …); }
 
 public class User {
 String getLogin();
 String getLocation();
 }
 
 public class Repo {
 String getName();
 String getDescription();
 String getLanguage();
 boolean isFork(); User getOwner();
 }
  9. GitHubService service = /* retrofit magic */
 
 service.getUsers(new Callback<List<User>>()

    {
 @Override
 public void success(List<User> users, Response response) {
 for (User user : users) {
 service.listRepos(user, new Callback<List<Repo>>() {
 @Override
 public void success(List<Repo> repos, Response response) {
 for (Repo repo : repos) {
 boolean isAndroidRepo = repo.getDescription().contains("android") || repo.getName().contains("android");
 boolean writtenInJava = repo.getLanguage().equals("Java");
 if (isAndroidRepo && writtenInJava && !repo.isFork()) {
 service.getUserDetails(user, new Callback<User>() {
 @Override
 public void success(User user, Response response) {
 if (user.getLocation().equals("Kraków")) {
 /* send invitation email */
 }
 }
 
 @Override
 public void failure(RetrofitError error) {
 }
 });
 break;
 }
 }
 }
 
 @Override
 public void failure(RetrofitError error) {
 }
 });
 }
 }
 
 @Override
 public void failure(RetrofitError error) {
 }
 });

  10. GitHubService service = /* retrofit magic */
 
 service.getUsers(new Callback<List<User>>()

    {
 @Override
 public void success(List<User> users, Response response) {
 for (User user : users) {
 service.listRepos(user, new Callback<List<Repo>>() {
 @Override
 public void success(List<Repo> repos, Response response) {
 for (Repo repo : repos) {
 boolean isAndroidRepo = repo.getDescription().contains("android") || repo.getName().contains("android");
 boolean writtenInJava = repo.getLanguage().equals("Java");
 if (isAndroidRepo && writtenInJava && !repo.isFork()) {
 service.getUserDetails(user, new Callback<User>() {
 @Override
 public void success(User user, Response response) {
 if (user.getLocation().equals("Kraków")) {
 /* send invitation email */
 }
 }
 
 @Override
 public void failure(RetrofitError error) {
 }
 });
 break;
 }
 }
 }
 
 @Override
 public void failure(RetrofitError error) {
 }
 });
 }
 }
 
 @Override
 public void failure(RetrofitError error) {
 }
 });

  11. GitHubService service = /* retrofit magic */
 
 for (User

    user : service.getUsers()) {
 for (Repo repo : service.listRepos(user)) {
 boolean isAndroidRepo =
 repo.getDescription().contains("android") ||
 repo.getName().contains("android");
 boolean writtenInJava = repo.getLanguage().equals("Java");
 if (isAndroidRepo && writtenInJava && !repo.isFork()) {
 if (service.getDetails(user).getLocation().equals("Kraków")) { /* send invitation email */
 }
 break;
 }
 }
 }
  12. GitHubService service = /* retrofit magic */
 
 service.getUsers().stream()
 .flatMap(user

    -> service.listRepos(user).stream())
 .filter(repo -> repo.getName().contains(“android") ||
 repo.getDescription().contains("android"))
 .filter(repo -> repo.getLanguage().equals("Java"))
 .filter(repo -> !repo.isFork())
 .map(Repo::getOwner)
 .distinct()
 .map(service::getUserDetails)
 .filter(user -> user.getLocation().equals("Kraków"))
 .forEach(user -> { /* send invitation email */ });
  13. public interface GitHubService {
 @GET("/users")
 void getUsers(Callback<List<User>> users);
 }
 service.getUsers(new

    Callback<List<User>>() {
 @Override
 public void success(List<User> users, Response response) {
 }
 
 @Override
 public void failure(RetrofitError error) {
 }
 });

  14. public interface GitHubService {
 @GET(“/users") Observable<List<User>> getUsers(); } 
 service.getUsers().subscribe(


    new Action1<List<User>>() {
 @Override
 public void call(List<User> users) { // onNext
 }
 },
 new Action1<Throwable>() {
 @Override
 public void call(Throwable throwable) { // onError
 }
 }
 );
  15. GitHubService service = /* retrofit magic */
 
 service.getUsers().stream()
 .flatMap(user

    -> service.listRepos(user).stream())
 .filter(repo -> repo.getName().contains(“android") ||
 repo.getDescription().contains("android"))
 .filter(repo -> repo.getLanguage().equals("Java"))
 .filter(repo -> !repo.isFork())
 .map(Repo::getOwner)
 .distinct()
 .map(service::getUserDetails)
 .filter(user -> user.getLocation().equals("Kraków"))
 .forEach(user -> { /* send invitation email */ });
  16. public interface GitHubService {
 @GET("/users")
 List<User> getUsers();
 
 @GET("/users/{user}")
 User

    getUserDetails(@Path("user") User user);
 
 @GET("/users/{user}/repos")
 List<Repo> listRepos(@Path("user") User user);
 }
 
 public class User {
 String getLogin();
 String getLocation();
 }
 
 public class Repo {
 String getName();
 String getDescription();
 String getLanguage();
 boolean isFork(); User getOwner();
 }
  17. public interface GitHubService {
 @GET("/users")
 Observable<List<User>> getUsers();
 
 @GET("/users/{user}")
 Observable<User>

    getUserDetails(@Path("user") User user);
 
 @GET("/users/{user}/repos")
 Observable<List<Repo>> listRepos(@Path("user") User user); }
 
 public class User {
 String getLogin();
 String getLocation();
 }
 
 public class Repo {
 String getName();
 String getDescription();
 String getLanguage();
 boolean isFork(); User getOwner();
 }
  18. GitHubService service = /* retrofit magic */
 
 service.getUsers()
 .flatMap(Observable::from)


    .flatMap(service::listRepos)
 .flatMap(Observable::from)
 .filter(repo -> repo.getName().contains("android") ||
 repo.getDescription().contains("android"))
 .filter(repo -> repo.getLanguage().equals("Java"))
 .filter(repo -> !repo.isFork())
 .map(Repo::getOwner)
 .distinct()
 .flatMap(service::getUserDetails)
 .filter(user -> user.getLocation().equals("Kraków"))
 .subscribe(user -> { /* send invitation email */ });
  19. GitHubService service = /* retrofit magic */
 
 service.getUsers().stream()
 .flatMap(user

    -> service.listRepos(user).stream()) 
 .filter(repo -> repo.getName().contains(“android") ||
 repo.getDescription().contains("android"))
 .filter(repo -> repo.getLanguage().equals("Java"))
 .filter(repo -> !repo.isFork())
 .map(Repo::getOwner)
 .distinct()
 .map(service::getUserDetails)
 .filter(user -> user.getLocation().equals("Kraków"))
 .forEach(user -> { /* send invitation email */ });
  20. GitHubService service = /* retrofit magic */
 
 service.getUsers()
 .flatMap(Observable::from)


    .flatMap(service::listRepos)
 .flatMap(Observable::from)
 .filter(repo -> repo.getName().contains("android") ||
 repo.getDescription().contains("android"))
 .filter(repo -> repo.getLanguage().equals("Java"))
 .filter(repo -> !repo.isFork())
 .map(Repo::getOwner)
 .distinct()
 .flatMap(service::getUserDetails)
 .filter(user -> user.getLocation().equals("Kraków"))
 .subscribe(user -> { /* send invitation email */ });
  21. GitHubService service = /* retrofit magic */
 
 service.getUsers()
 .flatMap(Observable::from)


    .flatMap(service::listRepos)
 .flatMap(Observable::from)
 .filter(repo -> repo.getName().contains("android") ||
 repo.getDescription().contains("android"))
 .filter(repo -> repo.getLanguage().equals("Java"))
 .filter(repo -> !repo.isFork())
 .map(Repo::getOwner)
 .distinct()
 .flatMap(service::getUserDetails)
 .filter(user -> user.getLocation().equals("Kraków"))
 .subscribe(user -> { /* send invitation email */ },
 error -> { /* report to analytics */ }
 );
  22. GitHubService service = /* retrofit magic */
 
 service.getUsers(new Callback<List<User>>()

    {
 @Override
 public void success(List<User> users, Response response) {
 for (User user : users) {
 service.listRepos(user, new Callback<List<Repo>>() {
 @Override
 public void success(List<Repo> repos, Response response) {
 for (Repo repo : repos) {
 boolean isAndroidRepo = repo.getDescription().contains("android") || repo.getName().contains("android");
 boolean writtenInJava = repo.getLanguage().equals("Java");
 if (isAndroidRepo && writtenInJava && !repo.isFork()) {
 service.getUserDetails(user, new Callback<User>() {
 @Override
 public void success(User user, Response response) {
 if (user.getLocation().equals("Kraków")) {
 /* send invitation email */
 }
 }
 
 @Override
 public void failure(RetrofitError error) {
 }
 });
 break;
 }
 }
 }
 
 @Override
 public void failure(RetrofitError error) {
 }
 });
 }
 }
 
 @Override
 public void failure(RetrofitError error) {
 }
 });

  23. RX!

  24. GitHubService service = /* retrofit magic */
 
 service.getUsers()
 .flatMap(Observable::from)


    .flatMap(service::listRepos)
 .flatMap(Observable::from)
 .filter(repo -> repo.getName().contains("android") ||
 repo.getDescription().contains("android"))
 .filter(repo -> repo.getLanguage().equals("Java"))
 .filter(repo -> !repo.isFork())
 .map(Repo::getOwner)
 .distinct()
 .flatMap(service::getUserDetails)
 .filter(user -> user.getLocation().equals("Kraków"))
 .subscribe(user -> { /* send invitation email */ },
 error -> { /* report to analytics */ }
 );
  25. GitHubService service = /* retrofit magic */
 
 service.getUsers()
 .flatMap(new

    Func1<List<User>, Observable<? extends User>>() {
 @Override
 public Observable<? extends User> call(List<User> users) {
 return Observable.from(users);
 }
 })
 .flatMap(new Func1<User, Observable<? extends List<Repo>>>() {
 @Override
 public Observable<? extends List<Repo>> call(User user) {
 return service.listRepos(user);
 }
 })
 .flatMap(new Func1<List<Repo>, Observable<? extends Repo>>() {
 @Override
 public Observable<? extends Repo> call(List<Repo> repos) {
 return Observable.from(repos);
 }
 })
 .filter(new Func1<Repo, Boolean>() {
 @Override
 public Boolean call(Repo repo) {
 return repo.getName().contains("android") ||
 repo.getDescription().contains("android");
 }
 })
 .filter(new Func1<Repo, Boolean>() {
 @Override
 public Boolean call(Repo repo) {
 return repo.getLanguage().equals("Java");
 }
 })
 .filter(new Func1<Repo, Boolean>() {
 @Override
 public Boolean call(Repo repo) {
 return !repo.isFork();
 }
 })
 .map(new Func1<Repo, User>() {
 @Override
 public User call(Repo repo) {
 return repo.getOwner();
 }
 })
 .distinct()
 .flatMap(new Func1<User, Observable<? extends User>>() {
 @Override
 public Observable<? extends User> call(User user) {
 return service.getUserDetails(user);
 }
 })
 .filter(new Func1<User, Boolean>() {
 @Override
 public Boolean call(User user) {
 return user.getLocation().equals("Kraków");
 }
 })
 .subscribe(
 new Action1<User>() {
 @Override
 public void call(User user) { /* send invitation email */ }
 },
 new Action1<Throwable>() {
 @Override
 public void call(Throwable error) { /* report to analytics */ }
 }
 );

  26. GitHubService service = /* retrofit magic */
 
 service.getUsers()
 .map(it

    -> { /* time consuming operation */ } )
 .subscribe( /* show users on UI */ );
  27. GitHubService service = /* retrofit magic */
 
 service.getUsers()
 .subscribeOn(Schedulers.io())


    .observeOn(Schedulers.computation())
 .map(it -> { /* time consuming operation */ } )
 .observeOn(AndroidSchedulers.mainThread())
 .subscribe( /* show users on UI */ );
  28. GitHubService service = /* retrofit magic */
 
 service.getUsers()
 .subscribeOn(Schedulers.io())


    .observeOn(Schedulers.computation())
 .map(it -> { /* time consuming operation */ } )
 .observeOn(AndroidSchedulers.mainThread())
 .subscribe( /* show users on UI */ ); subscribeOn(): “Create the Observable on this thread”
  29. observeOn(): “Perform all other operations on this thread” GitHubService service

    = /* retrofit magic */
 
 service.getUsers()
 .subscribeOn(Schedulers.io())
 .observeOn(Schedulers.computation())
 .map(it -> { /* time consuming operation */ } )
 .observeOn(AndroidSchedulers.mainThread())
 .subscribe( /* show users on UI */ );
  30. WidgetObservable
 .text(textView)
 .filter(t -> t.text().length() > 2)
 .debounce(300, TimeUnit.MILLISECONDS)
 .observeOn(Schedulers.io())


    .map( /* fetch suggestions */ )
 .observeOn(AndroidSchedulers.mainThread())
 .subscribe( /* show suggestions in autocomplete */ );
  31. WidgetObservable
 .text(textView)
 .filter(t -> t.text().length() > 2)
 .observeOn(Schedulers.io())
 .map( /*

    fetch suggestions */ )
 .observeOn(AndroidSchedulers.mainThread())
 .subscribe( /* show suggestions in autocomplete */ );
  32. Preconditions.checkState(isUserAMonkey()); WidgetObservable
 .text(textView)
 .filter(t -> t.text().length() > 2)
 .observeOn(Schedulers.io())
 .map(

    /* fetch suggestions */ )
 .observeOn(AndroidSchedulers.mainThread())
 .subscribe( /* show suggestions in autocomplete */ );
  33. https://github.com/ReactiveX/RxJava/wiki/Backpressure Preconditions.checkState(isUserAMonkey()); WidgetObservable
 .text(textView)
 .filter(t -> t.text().length() > 2)
 .observeOn(Schedulers.io())


    .map( /* fetch suggestions */ )
 .observeOn(AndroidSchedulers.mainThread())
 .subscribe( /* show suggestions in autocomplete */ );
  34. I want to… • load data in background, • process

    data in background, • cache processed data, • subscribe on changes and reload automatically… • …unless user left the UI which needs this data, in that case just mark the data as dirty and reload as needed, • have a basic backpressure handling.
  35. public class RxLoaderManager {
 public RxLoaderManager(Fragment fragment);
 
 public static

    abstract class LoaderBuilder<T> {
 public LoaderBuilder(int loaderId);
 protected abstract Loader<T> onCreateLoader(Context context);
 }
 
 public <T> Observable<T> load(LoaderBuilder<T> loaderBuilder); }
  36. GitHubService service = /* retrofit magic */
 
 service.getUsers()
 .flatMap(Observable::from)


    .flatMap(service::listRepos)
 .flatMap(Observable::from)
 .filter(repo -> repo.getName().contains("android") ||
 repo.getDescription().contains("android"))
 .filter(repo -> repo.getLanguage().equals("Java"))
 .filter(repo -> !repo.isFork())
 .map(Repo::getOwner)
 .distinct()
 .flatMap(service::getUserDetails)
 .filter(user -> user.getLocation().equals("Kraków"))
 .subscribe(user -> { /* send invitation email */ });
  37. GitHubService service = /* retrofit magic */
 
 service.getUsers()
 .flatMap()


    .flatMap()
 .flatMap()
 .filter( )
 .filter()
 .filter()
 .map()
 .distinct()
 .flatMap()
 .filter()
 .subscribe();
  38. GitHubService service = /* retrofit magic */
 
 Observable<T> t

    = service.getUsers()
 .flatMap()
 .flatMap()
 .flatMap(); Observable<U> u = t .filter() .map() .flatMap() .distinct(); Observable<V> v = t .filter() .map() .filter(); Observable.combineLatest(u, v) .distinct()
 .flatMap()
 .filter()
 .subscribe();
  39. GitHubService service = /* retrofit magic */
 
 Observable<T> t

    = service.getUsers()
 .flatMap()
 .flatMap()
 .flatMap(); Observable<U> u = t .filter() .map() .flatMap() .distinct(); Observable<V> v = t .filter() .map() .filter(); Observable.combineLatest(u, v) .distinct()
 .flatMap()
 .filter()
 .subscribe();
  40. /*
 RxFlowGraph
 deal ~> b1 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~> assoc_contact_ids.in1
 b3 ~> assoc_contact_ids.in2


    assoc_contact_ids.out ~> assoc_contacts_list ~> associated_contact_data.in1
 b1 ~> primary_contact ~~~> b3 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~> associated_contact_data.in2
 b1 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~> associated_contact_data.in3
 b1 ~> owner_id ~> b2 ~> users_with_contacts_permission ~> permissions_map.in1
 b2 ~> users_with_accounts_permission ~> permissions_map.in2
 permissions_map.out ~> associated_contact_data.in4
 associated_contact_data.out ~>
 
 .subscribe(
 onNext ->
 );
 */
 • Inspired by Akka Streams (http://goo.gl/gjG4FZ) • Java API & implementation ~ready, but a bit ugly • Big pain: RxFlowGraph elements != Observables • To be revisited if/when Kotlin is officially supported?
  41. “Well, Mr. Frankel, who started this program, began to suffer

    from the computer disease that anybody who works with computers now knows about. It's a very serious disease and it interferes completely with the work. The trouble with computers is you *play* with them. They are so wonderful. You have these switches - if it's an even number you do this, if it's an odd number you do that - and pretty soon you can do more and more elaborate things if you are clever enough, on one machine. After a while the whole system broke down. Frankel wasn't paying any attention; he wasn't supervising anybody. The system was going very, very slowly - while he was sitting in a room figuring out how to make one tabulator automatically print arc- tangent X, and then it would start and it would print columns and then bitsi, bitsi, bitsi, and calculate the arc-tangent automatically by integrating as it went along and make a whole table in one operation. Absolutely useless. We *had* tables of arc-tangents. But if you've ever worked with computers, you understand the disease - the *delight* in being able to see how much you can do. But he got the disease for the first time, the poor fellow who invented the thing.” ― Richard P. Feynman, Surely You're Joking, Mr. Feynman!
  42. ?