i'm trying to learn chain requests with RxJava + Retrofit + Kotlin.
The tutorial i was follwing was using RxJava 1.x, so when i try to re-implement with RxJava 2.x the i cannot get .zip() to work.
It's a simple app using Star Wars API, returning a list of movies, ando for each movie, get the characters from them.
fun loadMoviesFull(): Observable<Movie> {
return service.listMovies()
.flatMap { filmResults -> Observable.from(filmResults.results) }
.flatMap { film ->
Observable.zip(
Observable.just(Movie(film.title, film.episodeId, ArrayList<Character>())),
Observable.from(film.personUrls)
.flatMap { personUrl ->
service.loadPerson(Uri.parse(personUrl).lastPathSegment)
}
.map { person ->
Character(person!!.name, person.gender)
}
.toList(),
{ movie, characters ->
movie.characters.addAll(characters)
movie
})
}
}
If you want to see the whole implementation of the tutorial, this is the link(The comments are in portuguese):
http://www.nglauber.com.br/2017/03/rxjava-kotlin-retrofit-star-wars-api.html
I just want to know the syntax for ir, because i cannot implement on 2.x.
Thank you so much and i'm sorry for my bad english.
I don't know what error compiler produces but very probably your function should return Observable<List<Movie>>, zip's BiFunction requires passing types explicitly and should not have single movie at the end. And of course .toList() at the end.
Full code:
fun loadMoviesFull(): Observable<List<Movie>> {
return service.listMovies()
.flatMap { filmResults -> Observable.from(filmResults.results) }
.flatMap { film ->
Observable.zip(
Observable.just(Movie(film.title, film.episodeId, ArrayList<Character>())),
Observable.from(film.personUrls)
.flatMap { personUrl ->
service.loadPerson(Uri.parse(personUrl).lastPathSegment)
}
.map { person ->
Character(person!!.name, person.gender)
}
.toList(),
BiFunction<Movie, Character, Movie>{ movie, characters ->
movie.characters.addAll(characters)
})
}.toList()
}
It won't let me add a comment due to insufficient reputation, hence posting as an answer. Are you using rxkotlin? There are a number of helper functions, including Observable.zip() that help with the SAM ambiguity issues when using Rx2. Without knowing what your error is, that's the best advice I can give.
Related
Im currently developing an android app with Kotlin. Its my first experience with this programming language and im currently struggeling to translate an example of Java code to Kotlin.
I want to implement the answer to this question in Kotlin.
my current implementation fails to compile because the Ovservable::from method seems to be removed.
This is my current approach:
connectionObservable!!.flatMap { connection ->
connection.discoverServices()
.flatMap { services ->
services.getService(UUID.fromString("My UUID")).map(BluetoothGattService::getCharacteristics)
//here occurs the error, he wants a Single Source but got a observable with the ble characteristic
.flatMap { characteristics: MutableList<BluetoothGattCharacteristic> -> Observable.fromIterable(characteristics) }
.flatMap { characteristic: BluetoothGattCharacteristic ->
connection.setupNotification(characteristic)
.flatMap { observable: Observable<ByteArray> -> observable ,Pair<BluetoothGattCharacteristic, ByteArray>(characteristic, observable)}
}
}
}.subscribe { pair: Pair<BluetoothGattCharacteristic, ByteArray> ->
genericModel[pair.first.uuid] = pair.second
throwable -> { /* handle errors */ }
}
Can you point out my errors so i can understand what im doing wrong?
Thanks in advance!
Jonas
There are several potential issues with your code.
First of all the code is not syntactically correct — see this line:
.flatMap { observable: Observable<ByteArray> -> observable ,Pair<BluetoothGattCharacteristic, ByteArray>(characteristic, observable)}
I assume you probably wanted to flatMap the observable: Observable<ByteArray> (type added by me for clarity) to get from it ByteArray objects. This would look like this:
.flatMap { observable: Observable<ByteArray> -> observable.map { bytes -> Pair(characteristic, bytes) }}
Additionally the code will not compile as you try to return an Observable from a lambda which expects a SingleSource — exactly as your compiler says. If you have a Single and you will .flatMap it — it's lambda is supposed to return another SingleSource. There is a .flatMapObservable function that expects ObservableSource which is what you should use. The end result would look like:
connectionObservable!!.flatMap { connection ->
connection.discoverServices()
.flatMapObservable { services ->
services.getService(UUID.fromString("My UUID")).map(BluetoothGattService::getCharacteristics)
//here occurs the error, he wants a Single Source but got a observable with the ble characteristic
.flatMapObservable { characteristics: MutableList<BluetoothGattCharacteristic> -> Observable.fromIterable(characteristics) }
.flatMap { characteristic: BluetoothGattCharacteristic ->
connection.setupNotification(characteristic)
.flatMap { observable: Observable<ByteArray> -> observable.map { bytes -> Pair(characteristic, bytes) }}
}
}
}.subscribe { pair: Pair<BluetoothGattCharacteristic, ByteArray> ->
genericModel[pair.first.uuid] = pair.second
throwable -> { /* handle errors */ }
}
Observable.fromIterable() is still the API of Observable. You probably do not use the Observable class from proper package. There is an Observable class in package java.util but we are using here one from RxJava 2 which has package io.reactivex
I am using RxJava to get list of Posts from JSONplaceholder api.
https://jsonplaceholder.typicode.com/posts
I want to take only the top 10 from the list and save in the data base.
I am aware I need to use take operator but cannot figure out how to use that with concatMap.
Here is what I have already.
private fun loadPosts(){
subscription = Observable.fromCallable { postDao.all }
.concatMap { dbPostList ->
postApi.getPosts().concatMap { apiPostList ->
//HERE i ONLY WANT TO TAKE 10 ITEMS AND SAVE IT (HOW CAN I USE TAKE OPERATOR HERE)
postDao.insertAll(*apiPostList.toTypedArray())
Observable.just(apiPostList)
}
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe { onRetrievePostListStart() }
.doOnTerminate { onRetrievePostListFinish() }
.subscribe(
{ result -> onRetrievePostListSuccess(result) },
{ onRetrievePostListError() }
)
}
Below code I tried and it does not work as expected.
postApi.getPosts()
.take(10) // DOES NOT WORK
.concatMap { apiPostList ->
postDao.insertAll(*apiPostList.toTypedArray())
Observable.just(apiPostList)
}
getPosts() returns a list. To use take(10) in your case you'd have to emit each element of the list individual. However, since you emit the entire list in one go, it is as if take(10) is trying to take 10 lists of posts rather than 10 posts.
I can think of 2 ways to fix this. You can convert the list to and observable like:
postApi.getPosts()
.flatMap { Observable.fromIterable(it) }
.take(10)
.toList()
Emit each item of the list, take 10 of them and collect the results in a list ready for your concatMap.
Another option is to manually slice the list:
postApi.getPosts()
.map { it.slice(0 until 10) }
Not so rx-ish but still should work.
Careful because both approaches assume there are at least 10 items in the list.
While iterating and fetching web responses the chain stops when it encounters an error.
I used .onErrorResumeNext(Observable.empty()) to keep the iteration going but want to do some error handling too. How can this be done?
.getRepos()
.flatMap { itemList ->
//fetches all repos
Observable.fromIterable(itemList)
}
.concatMapEager { githubItem ->
//fetches commits of each repos
networkModule.getCommits().map { commitItem ->
dataBaseModule.insertRepo(commitItem)
}.onErrorResumeNext(Observable.empty())
//here I want to take some action instead of just passing an empty observable
}
You can use the doOnError() operator just before the onErrorResumeNext() to perform an action.
...
.doOnError( error -> {} )
.onErrorResumeNext( Observable.empty() )
...
I want to call multiple Rest Api's in a Sequence and having each Response Dto is different from each other.
Please help me to get rid from this situation that, How can i call these Api's using Rx Java Observables in Android.
no, you should use map() or doOnNext(), it will look like this
Observable.just(1)
.doOnNext(value -> {
someRequestX().execute();
})
.map(value -> {
return nextRequestY().execute();
})
.doOnNext(requestYResponse-> {
someRequesZ(requestYResponse.someValue).execute();
})
.map(requestYResponse-> {
return someRequesK(requestYResponse.someValue).execute();
})
.map(requestKResponse -> {
return someRequesJ(requestKResponse.someValue).execute();
})
.subscribe(requestJResponse -> {
doSOmethingWithFinalResponse(requestJResponse );
})
First of all, for network requests is better to use Single then Observable, because there always will be only one item. To switch from one requests to another, you can use flatMap.
Assuming your code is similar, you can try this:
class Dto1 {}
class Dto2 {}
class Dto3 {}
public interface Api {
Single<Dto1> getDto1();
Single<Dto2> getDto2();
Single<Dto3> getDto3();
}
private Api api;
public void callApi() {
api.getDto1()
.doOnSuccess(dto1 -> {/*do something with dto1*/})
.flatMap(dto1 -> api.getDto2())
.doOnSuccess(dto2 -> {/*do something with dto2*/})
.flatMap(dto2 -> api.getDto3())
.doOnSuccess(dto3 -> {/*do something with dto3*/})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe()
}
For the same scenario, i use concat operator which takes multiple Observables and concatenates their sequence
If response sequence doesn't require then you can use merge operator also.
Concat VS Merge operaror
try doOnNext() or map() method of Observable and use sync execute() of each response and pass them further
i am using Retrofit with RxJAva for an app that gets Rss Feeds, but the rss doesn't contain all the informations so i use jsoup to parse every item link, to retrieve the image and the article's description. now i am using it this way:
public Observable<Rss> getDumpData() {
return newsAppService.getDumpData()
.flatMap(rss -> Observable.from(rss.channel.items)
.observeOn(Schedulers.io())
.flatMap(Checked.f1(item -> Observable.just(Jsoup.connect(item.link).get())
.observeOn(Schedulers.io())
.map(document -> document.select("div[itemprop=image] > img").first())
.doOnNext(element -> item.image = element.attr("src"))
)))
.defaultIfEmpty(rss)
.ignoreElements()
.observeOn(Schedulers.io())
.subscribeOn(AndroidSchedulers.mainThread());
}
and i am getting an error on this line: defaultIfEmpty(rss)
it doesn't recognize rss of the flatmap. and when i move the defaultIfEmpty(rss) in flatmap brackets i have another error saying that the return type must be changed to Element. is their any solution ?
first of all you need to get rid of all the concurrency with observeOn and use subscribeOn.
.observeOn(Schedulers.io())
Please consider using observeOn with AndroidScheduler if want to sync back data from another thread back to the event-loop. Normally you would use observeOn before subscribing to a observable in order to sync back to ui-loop and change ui-information.
.observeOn(AndroidSchedulers.mainThread())
Secondly it is not recommended to mutate objects in the pipeline. You should return a new object very time.
.doOnNext(element -> item.image = element.attr("src"))
I tried to refactor your solution under consideration of the first two points. I am using RxJava2-RC5
The flatMap operator has many overloades. One of them provides a function to zip together the incoming value and the created value.
Observable<Rss> rssItemObservable = newsService.getDumpData()
.flatMap(rss -> getRssItemInformation(rss).subscribeOn(Schedulers.io()),
(r, rItemList) -> {
Rss rInterim = new Rss();
rInterim.items = rItemList;
return rInterim;
});
Helping-method for retrieving information for each item in Rss. Please consider using the overload with maxConcurrency, because on default it will subscribe to every stream at once. Therefore flatMap would create many http-requests.
private Observable<List<RssItem>> getRssItemInformation(Rss rss) {
return Observable.fromIterable(rss.items)
.flatMap(rssItem -> getImageUrl(rssItem).subscribeOn(Schedulers.io()), (rItem, img) -> {
RssItem item = new RssItem();
printCurrentThread("merge1");
item.image = img;
item.link = rItem.link;
return item;
}).toList().toObservable();
}
Helping-method for retrieving the image url. Returning observable is not opinionated about concurrency. If an error occurs, an empty string will be returned as default value.
private Observable<String> getImageUrl(String link) {
return Observable.fromCallable(() -> Jsoup.connect(link).get())
.map(document -> document.select("div[itemprop=image] > img").first())
.map(element -> element.attr("src"))
.onErrorResumeNext(throwable -> {
return Observable.just("");
});
}
You may look at the full example at github.gist: https://gist.github.com/anonymous/a8e36205fc2430517c66c802f6eef38e
You can't mix internal parameter of one RxJava parameter (flatMap lambda parameter) with another operator parameter (defaultIfEmpty).
First of all, create a helper function to keep main reactive stream cleaner:
private Observable<List<Item>> getDetails(List<Item> items) {
return Observable.from(items)
.observeOn(Schedulers.io())
.flatMap(Checked.f1(item ->
Observable.zip(
Observable.just(item),
Observable.just(Jsoup.connect(item.link).get())
.observeOn(Schedulers.io())
.map(document -> document.select("div[itemprop=image] > img").first()),
(itemInner, element) -> {
itemInner.image = element.attr("src");
return itemInner;
}
)
))
.toList();
}
Then reformat main function:
newsAppService.getDumpData()
.flatMap(rss ->
Observable.zip(
Observable.<Rss>just(rss),
getDetails(rss.channel.items),
(rssInner, items) -> {
rssInner.channel.items = items;
return rss;
}).onErrorResumeNext((throwable -> Observable.just(rss))
)
)
.observeOn(Schedulers.io())
.subscribeOn(AndroidSchedulers.mainThread());
Hope I got your aim properly. It may not work, as I am unable to test it, however I hope you get the idea. The reason I used .zip functions it that you can't loose the reference to the currently parsed item or rss