Slide 1

Slide 1 text

RX

Slide 2

Slide 2 text

chalup.github.io ! " + @jchalupski +Jerzy Chalupski

Slide 3

Slide 3 text

RX?

Slide 4

Slide 4 text

public interface GitHubService {
 @GET("/users")
 List getUsers();
 
 @GET("/users/{user}/repos")
 List listRepos(@Path("user") User user);
 }
 
 public class User {
 String getLogin();
 }
 
 public class Repo {
 String getDescription(); User getOwner();
 }

Slide 5

Slide 5 text

GitHubService service = /* retrofit magic */
 
 for (User user : service.getUsers()) {
 for (Repo repo : service.listRepos(user)) {
 if (repo.getDescription().contains("android")) {
 /* send invitation email */
 break;
 }
 }
 }

Slide 6

Slide 6 text

public interface GitHubService {
 @GET("/users")
 List getUsers();
 
 @GET("/users/{user}")
 User getUserDetails(@Path("user") User user);
 
 @GET("/users/{user}/repos")
 List 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();
 }

Slide 7

Slide 7 text

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;
 }
 }
 }

Slide 8

Slide 8 text

public interface GitHubService {
 @GET("/users")
 List getUsers();
 
 @GET("/users/{user}")
 User getUserDetails(@Path("user") User user);
 
 @GET("/users/{user}/repos")
 List 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();
 }

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

GitHubService service = /* retrofit magic */
 
 service.getUsers(new Callback>() {
 @Override
 public void success(List users, Response response) {
 for (User user : users) {
 service.listRepos(user, new Callback>() {
 @Override
 public void success(List 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() {
 @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) {
 }
 });


Slide 11

Slide 11 text

GitHubService service = /* retrofit magic */
 
 service.getUsers(new Callback>() {
 @Override
 public void success(List users, Response response) {
 for (User user : users) {
 service.listRepos(user, new Callback>() {
 @Override
 public void success(List 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() {
 @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) {
 }
 });


Slide 12

Slide 12 text

FP 101

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

filter( )

Slide 15

Slide 15 text

filter( )

Slide 16

Slide 16 text

filter( ) map( )

Slide 17

Slide 17 text

filter( ) map( )

Slide 18

Slide 18 text

filter( ) map( ) flatMap( )

Slide 19

Slide 19 text

filter( ) map( ) flatMap( )

Slide 20

Slide 20 text

filter map flatMap Java 8 Stream API

Slide 21

Slide 21 text

filter transform transformAndConcat Guava FluentIterable

Slide 22

Slide 22 text

select map flat_map Ruby Enumerable

Slide 23

Slide 23 text

Where Select SelectMany C# LINQ

Slide 24

Slide 24 text

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;
 }
 }
 }

Slide 25

Slide 25 text

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 */ });

Slide 26

Slide 26 text

RX 101

Slide 27

Slide 27 text

TAKEAWAY #1 Observable == Iterable

Slide 28

Slide 28 text

No content

Slide 29

Slide 29 text

No content

Slide 30

Slide 30 text

No content

Slide 31

Slide 31 text

No content

Slide 32

Slide 32 text

No content

Slide 33

Slide 33 text

No content

Slide 34

Slide 34 text

No content

Slide 35

Slide 35 text

No content

Slide 36

Slide 36 text

No content

Slide 37

Slide 37 text

No content

Slide 38

Slide 38 text

No content

Slide 39

Slide 39 text

public interface GitHubService {
 @GET("/users")
 void getUsers(Callback> users);
 }
 service.getUsers(new Callback>() {
 @Override
 public void success(List users, Response response) {
 }
 
 @Override
 public void failure(RetrofitError error) {
 }
 });


Slide 40

Slide 40 text

public interface GitHubService {
 @GET(“/users") Observable> getUsers(); } 
 service.getUsers().subscribe(
 new Action1>() {
 @Override
 public void call(List users) { // onNext
 }
 },
 new Action1() {
 @Override
 public void call(Throwable throwable) { // onError
 }
 }
 );

Slide 41

Slide 41 text

filter map flatMap RxJava Operators

Slide 42

Slide 42 text

filter map flatMap RxJava Operators http://reactivex.io/documentation/operators.html

Slide 43

Slide 43 text

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 */ });

Slide 44

Slide 44 text

public interface GitHubService {
 @GET("/users")
 List getUsers();
 
 @GET("/users/{user}")
 User getUserDetails(@Path("user") User user);
 
 @GET("/users/{user}/repos")
 List 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();
 }

Slide 45

Slide 45 text

public interface GitHubService {
 @GET("/users")
 Observable> getUsers();
 
 @GET("/users/{user}")
 Observable getUserDetails(@Path("user") User user);
 
 @GET("/users/{user}/repos")
 Observable> 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();
 }

Slide 46

Slide 46 text

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 */ });

Slide 47

Slide 47 text

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 */ });

Slide 48

Slide 48 text

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 */ });

Slide 49

Slide 49 text

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 */ }
 );

Slide 50

Slide 50 text

GitHubService service = /* retrofit magic */
 
 service.getUsers(new Callback>() {
 @Override
 public void success(List users, Response response) {
 for (User user : users) {
 service.listRepos(user, new Callback>() {
 @Override
 public void success(List 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() {
 @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) {
 }
 });


Slide 51

Slide 51 text

RX!

Slide 52

Slide 52 text

RX! db access user input touch events validation web requests

Slide 53

Slide 53 text

No content

Slide 54

Slide 54 text

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 */ }
 );

Slide 55

Slide 55 text

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


Slide 56

Slide 56 text

! orfjackal/retrolambda

Slide 57

Slide 57 text

! evant/gradle-retrolambda

Slide 58

Slide 58 text

No content

Slide 59

Slide 59 text

No content

Slide 60

Slide 60 text

RX @ $

Slide 61

Slide 61 text

! ReactiveX/RxAndroid

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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 */ );

Slide 64

Slide 64 text

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”

Slide 65

Slide 65 text

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 */ );

Slide 66

Slide 66 text

! dlew/android-subscription-leaks

Slide 67

Slide 67 text

AppObservable.bindFoo() Simple & composable May leak

Slide 68

Slide 68 text

RxActivity / RxFragment Never leaks Requires base class

Slide 69

Slide 69 text

Never leaks Requires base class RoboMapsListViewSupportObservableActiv RxActivity / RxFragment

Slide 70

Slide 70 text

! ReactiveX/RxAndroid

Slide 71

Slide 71 text

WidgetObservable ViewObservable ContentObservable

Slide 72

Slide 72 text

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 */ );

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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 */ );

Slide 75

Slide 75 text

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 */ );

Slide 76

Slide 76 text

RX @ !

Slide 77

Slide 77 text

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.

Slide 78

Slide 78 text

! futuresimple/android-db-commons

Slide 79

Slide 79 text

public class RxLoaderManager {
 public RxLoaderManager(Fragment fragment);
 
 public static abstract class LoaderBuilder {
 public LoaderBuilder(int loaderId);
 protected abstract Loader onCreateLoader(Context context);
 }
 
 public Observable load(LoaderBuilder loaderBuilder); }

Slide 80

Slide 80 text

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 */ });

Slide 81

Slide 81 text

GitHubService service = /* retrofit magic */
 
 service.getUsers()
 .flatMap()
 .flatMap()
 .flatMap()
 .filter( )
 .filter()
 .filter()
 .map()
 .distinct()
 .flatMap()
 .filter()
 .subscribe();

Slide 82

Slide 82 text

GitHubService service = /* retrofit magic */
 
 Observable t = service.getUsers()
 .flatMap()
 .flatMap()
 .flatMap(); Observable u = t .filter() .map() .flatMap() .distinct(); Observable v = t .filter() .map() .filter(); Observable.combineLatest(u, v) .distinct()
 .flatMap()
 .filter()
 .subscribe();

Slide 83

Slide 83 text

GitHubService service = /* retrofit magic */
 
 Observable t = service.getUsers()
 .flatMap()
 .flatMap()
 .flatMap(); Observable u = t .filter() .map() .flatMap() .distinct(); Observable v = t .filter() .map() .filter(); Observable.combineLatest(u, v) .distinct()
 .flatMap()
 .filter()
 .subscribe();

Slide 84

Slide 84 text

/*
 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?

Slide 85

Slide 85 text

RECAP

Slide 86

Slide 86 text

Observable == Iterable

Slide 87

Slide 87 text

GOOD Escape from callback hell

Slide 88

Slide 88 text

GOOD Single point of error handling

Slide 89

Slide 89 text

GOOD Highlights spaghetti code

Slide 90

Slide 90 text

GOOD Highlights sloppy state usage

Slide 91

Slide 91 text

BAD Lifecycle handling

Slide 92

Slide 92 text

UGLY Anonymous classes boilerplate

Slide 93

Slide 93 text

HOT or NOT?

Slide 94

Slide 94 text

HOT or NOT? HOT in WidgetObservables

Slide 95

Slide 95 text

HOT or NOT? HOT in Retrofit

Slide 96

Slide 96 text

HOT or NOT? HOT in complex db access

Slide 97

Slide 97 text

HOT or NOT? NOT hot everywhere

Slide 98

Slide 98 text

“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!

Slide 99

Slide 99 text

?

Slide 100

Slide 100 text

Thanks.