Reactive programming with RxJava
- 6. What is RxJava?
- A library for composing asynchronous and event based programs
We can write concurrent code using RxJava library without worrying about low
level threads, locks and synchronization.
- 7. Brief history or Rx
Reactive Extensions: first introduced by Microsoft
RxJava: the jvm implementation was first developed by Netflix
Current version is 2.x
(I’ll talk about version 1.x here)
- 13. Observable<T>
… a flowing sequence of values
… a stream of events
An observer can subscribe to the Observable
The Observable may call onNext(), onError() or onCompleted() on
the observer
- 15. We all know Iterables, right?
Iterable<T> {
Iterator<T> iterator();
}
Iterator<T> {
Boolean hasNext();
T next();
}
- 16. Observable duality with Iterable
Iterable<T>, Iterator<T>
● Get an iterator
● hasNext(), next()
● Pull based
● Sync
Observable<T>, Observer<T>
● Subscribe an observer
● onNext(), onError(), onCompleted()
● Push based
● Async
- 18. Hello Observable!
Let’s create an Observable<String> that will emit “Hello, world!”
Observable<String> hello = Observable.create(obs -> {
obs.onNext(“Hello, world!”);
});
- 19. And we can subscribe to it ...
Observable<String> hello = Observable.create(obs -> {
obs.onNext(“Hello, world!”);
});
hello.subscribe(s -> {
System.out.println(s);
});
- 20. We can simplify the creation here
Observable<String> hello = Observable.just(“Hello, World!”);
- 21. We can also handle errors and completed event
Observable<String> hello = Observable.just(“Hello, World!”);
hello.subscribe(
val -> {System.out.println(val);},
error -> {System.out.println(“Error occurred”);},
() -> { System.out.println(“Completed”)};
})
- 22. We’ve seen how we can create
Observables, and how to consume them.
- 27. Observable.map example
// We have a function that gives us an Observable of ints
Observable<Integer> ids = searchForArticles(“dhaka”);
// another function that takes an int, and returns an article
Article loadArticle(Integer articleId) {...}
Observable<Article> result = ids.map(id -> loadArticle(id));
- 29. Observable.flatMap example
// We have a function that gives us an Observable of ints
Observable<Integer> ids = searchForArticles(“dhaka”);
// and a function returns an article wrapped inside Observable
Observable<Article> loadArticle(Integer articleId) {...}
Observable<Article> result = ids.flatMap(id -> loadArticle(id));
- 31. Map vs. flatMap example
Observable<Integer> ids = searchForArticles(“dhaka”);
Observable<Article> loadArticle(Integer articleId) {...}
Observable<Observable<Article>> res = ids.map(this::loadArticle);
Observable<Article> result = ids.flatMap(this::loadArticle);
- 35. A real world example
Let’s assume we are working for a news service. And we have the following
requirements.
● Do a search for a term on the search server. The search server will return
some ids
● For each id load the news article from db.
● For each id find out how many likes the article has on social network
● Merge the result from above two steps and send it to view layer
- 36. Let’s assume we have the following (sync) API
List<Integer> searchForArticles(String query)
PersistentArticle loadArticle(Integer articleId)
Integer fetchLikeCount(Integer articleId)
…. and we can create article by using the db object and like count
Article result = new Article(PersistentArticle, Integer)
- 37. One way of solving it
List<Integer> searchResult = searchForArticles(“dhaka”);
List<Article> result = new ArrayList<>();
for(Integer id : searchResult) {
PersistentArticle pa = loadArticle(id)
Integer likes = fetchLikeCount(id)
result.add(new Article(pa, likes));
}
return result;
- 41. When an article is loading, nothing
prevents us from fetching the like count
of that article,
or loading another article, right?
- 45. Let’s assume now we have an async API
Observable<Integer> searchForArticles(String query)
Observable<PersistentArticle> loadArticle(Integer articleId)
Observable<Integer> fetchLikeCount(Integer articleId)
And we need to come up with the result as the following type.
Observable<Article> result = … ;
// We can use the result like following
result.subscribe(article -> {//update the view with it});
- 46. Don’t worry about how the async API
was implemented. Just assume it is
present.
(If we only have a sync api, we can still make it async by wrapping things inside
Observables, using Observable.create, just etc.)
- 47. First step: do the search
Observable<Integer> ids = searchForArticles(String query);
….
For each id we need to load the article and fetch the like count.
- 48. How do we get the ids out from the
Observable?
- 49. We don’t, instead we apply some
operator to transform the source
Observable to a different one
- 50. Let’s go back to few slides ago, and see
how we solved the problem sequentially
- 51. The traditional solution ...
List<Integer> searchResult = searchForArticles(“dhaka”);
List<Article> result = new ArrayList<>();
for(Integer id : searchResult) {
PersistentArticle pa = loadArticle(id)
Integer likes = fetchLikeCount(id)
result.add(new Article(pa, likes));
}
return result;
- 55. Many Observable operators take lambda
as parameter.
And we can get the item from inside the
Observable box as a parameter of our
supplied lambda.
- 56. In case of Observable.filter
Observable<Integer> numbers = Observable.just(1, 2, 3, 4, 5, 6);
numbers.filter(num -> num % 2 == 0);
// The Observable.filter operator takes a lambda as parameter
// We pass it a lambda
// Our supplied lambda will be called for each of the item
// So here the parameter “num” will represent an item inside the box
// This is how we get things out from Observable box.
- 57. So, let’s get back to the original question
Observable<Integer> ids = searchForArticles(String query);
….
For each id we need to load the article and fetch the like count.
- 58. Now we know we need to apply some
operator, which one?
- 59. We have to apply flatMap
Observable<Integer> ids = searchForArticles(query);
ids.flatMap(id -> {
// do something with the id
...
});
So, by applying flatMap, we somehow get the item from inside the Observable
box, as the parameter of our lambda.
- 60. We have to apply flatMap
Observable<Integer> ids = searchForArticles(query);
ids.flatMap(id -> {
Observable<PersistentArticle> arts = loadArticle(id);
Observable<Integer> likes = fetchLikeCount(id);
//how do we get the article out from inside Observable?
});
- 61. We need to apply flatMap again ...
Observable<Integer> ids = searchForArticles(query);
ids.flatMap(id -> {
Observable<PersistentArticle> arts = loadArticle(id);
Observable<Integer> likes = fetchLikeCount(id);
return arts.flatMap(art -> {
// and now we need to get the likes out
});
})
- 62. And flatMap again ...
Observable<Integer> ids = searchForArticles(query);
ids.flatMap(id -> {
Observable<PersistentArticle> arts = loadArticle(id);
Observable<Integer> likes = fetchLikeCount(id);
return arts.flatMap(art -> {
return likes.flatMap(like -> {
// now we have everything to make an Article object
// so what do we return here? A new Article()?
});
});
})
- 63. We need to wrap the result inside Observable
Observable<Integer> ids = searchForArticles(query);
ids.flatMap(id -> {
Observable<PersistentArticle> arts = loadArticle(id);
Observable<Integer> likes = fetchLikeCount(id);
return arts.flatMap(art -> {
return likes.flatMap(like -> {
return Observable.just(new Article(art, like));
});
});
})
- 66. Alternate version using zip
Observable<Integer> ids = searchForArticles(query);
ids.flatMap(id -> {
Observable<PersistentArticle> arts = loadArticle(id);
Observable<Integer> likes = fetchLikeCount(id);
return Observable.zip(arts, likes, (art, lc) -> {
return new Article(art, lc);
});
});
})
- 67. Using the full power of Java 8 …
searchForArticles(query).flatMap(id -> {
return zip(loadArticle(id),
fetchLikeCount(id),
Article::new);
});
});
- 68. Using the full power of Java 8 …
searchForArticles(query).flatMap(id -> {
return zip(loadArticle(id),
fetchLikeCount(id),
Article::new);
});
});
This is the solution we desire. I find this code beautiful. And it’s (mostly)
concurrent (depending on the implementation of the search, load, etc.).
- 69. Keep these in mind while using RxJava
● Observables can emit zero, one or more items
● Observables can be of infinite stream of values/events
● Observables can complete without returning anything, Observable.empty().
● Observables can emit some items and then call onError() to terminate
abnormally
● If onError() is called, then onCompleted will not be called
- 70. Keep these in mind while using RxJava
● Observables are by default lazy. If no subscriber is subscribed, then nothing
will be executed.
● By default they run in the same thread from which the subscriber is called
● You can change the subscriber thread by calling subscribeOn()
● You can change the observer thread by calling observeOn()
● There are some built in Schedulers (similar to thread pool). For example
Schedulers.io(), Schedulers.computation().
● A single Observable issues notifications to observers serially (not in parallel).
- 71. A better version of the previous code
searchForArticles(query).flatMap(id -> {
return Observable.just(id)
.subscribeOn(Schedulers.io())
.flatMap(i ->{
return zip(loadArticle(i),
fetchLikeCount(i),
Article::new);
});
});
});
In fact this is the concurrent version, although it lost it’s beauty a bit :-)
Hoping to cover this in a future session.
- 72. When you have multiple async or
concurrent things, depending on each
other, RxJava may be a good candidate.
- 74. Advanced topics (maybe in some future sessions)
A whole lot more operators
Subjects
Schedulers
Backpressure
Implementing custom operators
- 75. Have a look into
● CompletableFuture (introduced in java 8)
● Reactive streams specification (which RxJava 2.x implemented)
● java.util.concurrent.Flow api (coming in java 9)
- 76. Reference
● Collection of all tutorials, http://reactivex.io/tutorials.html
● Reactive programming with RxJava book,
http://shop.oreilly.com/product/0636920042228.do
● The RxJava contract, http://reactivex.io/documentation/contract.html