Convert two RxJava requests to one request - android

I have two requests that I execute using RxJava. They should run one after the other. This is what I have at the current moment :
fun showPhotos() {
_liveData.postValue(Resource.Loading())
compositeDisposable.add(useCase.getPhotos()
.subscribe({
showPosts(it)
}) {
_liveData.postValue(Resource.Failure(it.localizedMessage))
Timber.e(it)
})
}
private fun showPosts(networkPhotos: List<NetworkPhoto>) {
compositeDisposable.add(useCase.getPost()
.subscribe({ networkPosts ->
_liveData.postValue(
Resource.Success(PostAndImages(networkPosts, networkPhotos).asDomaineModel())
)
}) {
_liveData.postValue(Resource.Failure(it.localizedMessage))
Timber.e(it)
})
}
Is there any solution that I can make one RxJava call instead of executing two requests sequentially ?

If you need to run 2 queries sequentially, you can use the flatMap operator.
data class RequestWrapper(var photos: YourType? = null, var networkPosts : YourType? = null)
fun sequentiallyRequest(){
val requestWrapper = RequestWrapper()
useCase.getPhotos()
.map{requestWrapper.photos= it}
.flatMap{useCase.getPost()}
.map{requestWrapper.networkPosts = it}
.subscribe({
_liveData.postValue(
Resource.Success(PostAndImages(requestWrapper.networkPosts, networkPhotos).asDomaineModel())
)
})
Or, use operator zip. But in this case, the requests will be executed in parallel.
Single.zip(
useCase.getPhotos(),
useCase.getPost(),
Pair::new)
.subscribe(pair -> {
showPosts(pair.first)
_liveData.postValue(
Resource.Success(PostAndImages(pair.second, networkPhotos).asDomaineModel())
}

You can use .flatMap() if one depends on another or .zip() if you just need to run them both at once and merge them after both completed

You may using zip. Queries will work in order.
Single.Zip(
useCase.getPhotos().subscribeOn(Schedulers.newThread()),
useCase.getPost().subscribeOn(Schedulers.newThread()),
BiFunction { photos: ????, posts: ???? -> Pair(photos, posts) }
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
it.first is photos
it.second is posts
},{
error
})

Related

Android. RxJava 2: Parallel multiple network calls

I need make two parallel requests with RxJava. For this I use zip operator. Here is my code:
public Disposable getBooksAndAuthors(String id, ReuqestCallback requestCallback) {
return singleRequest(Single.zip(
getBooks(id).subscribeOn(Schedulers.newThread()).observeOn(AndroidSchedulers.mainThread()),
getAuthors(id).subscribeOn(Schedulers.newThread()).observeOn(AndroidSchedulers.mainThread()),
(book, author) -> new ZipResponseWrapper(book, author).getResponse()), requestCallback);
}
private <T extends NetworkResponse> Disposable singleRequest(Single<T> single, RequestCallback requestCallback) {
return single.doOnSubscribe(d -> requestCallback.onStartRequest())
.doOnSuccess(s -> requestCallback.onSuccess(s))
.doOnError(ErrorConsumer.consume((t) -> requestCallback.onError(t)))
.doFinally(() -> requestCallback.onFinish())
.subscribe();
}
But I don’t understand how to receive response separately for each request. That is, I need to, if the answer came to the first request, immediately display the data received from this request and not wait for a response to the second request. And after the answer to the second request arrives, display the data received on the second request.This is necessary due to the fact that the second request fulfills a long time. Please help me.
Here is an example of how you can handle it with the responses for each function:
val disposable = Observable.zip(
firstNetworkCall().subscribeOn(Schedulers.io()),
secondNetworkCall().subscribeOn(Schedulers.io()),
BiFunction{
firstResonse: ResponseOneType,
secondResponse: ResponseTwoType ->
combineResult(firstResponse, secondResponse) }))
.observeOn(AndroidSchedulers.mainThread())
.subscribe { it -> doSomethingWithIndividualResponse(it) }
My suggestion (in Kotlin though):
val id = 0L
Observables.combineLatest(
getBooks(id).startWith(emptyList<Book>()).subscribeOn(Schedulers.io()).observeOn(Schedulers.computation()),
getAuthor(id).startWith(emptyList<Author>()).subscribeOn(Schedulers.io()).observeOn(Schedulers.computation())
) { book: List<Book>, author: List<Author> ->
Pair(book, author)
}.skip(1)
.observeOn(AndroidSchedulers.mainThread())
.subscribe { (books: List<Book>, authors: List<Author>) ->
view.show(books)
view.show(authors)
}

How can I use the response result of an RXJava call as a condition for an if statement inside another RXJava function?

I am making 2 RX calls that are nested within each other and are co-dependent. There is an issue with the server (which cannot be solved right now for various reasons) which returns errors in the 2nd nested call.
Until this gets solved, I need to have it that if the 2nd call returns an error, the results of the first call are discarded as well. Right now, the entire iterative process stops the moment any of these error responses occur, and so my goal is to skip over them.
Here is what my call structure currently looks like:
fun getAllDynamicUtterances(module: String) {
var uttList: ArrayList<DynamicUtterance>? = ArrayList()
rxSubs?.add(
repository.getDynamicUtterances(module).map{res ->
res.uttSets.forEach {utt ->
utt.module = res.module!!
utt.transferInputValues()
utt.generateDefaultFlatTree()
uttList?.add(utt)
insertDynamicUtterance(utt)
repository.updateDynamicUtteranceView(utt).blockingForEach {
utt.assignSelectionStrings(it)
repository.storeDynamicUttPieces(utt.inputUttPieces)
utt.uttLinearisations = it.linearisations
updateDynamicUtterance(utt)
}
}
}.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()).subscribe ({
allDynamicUtterances?.postValue(uttList)
},{
Log.e("errorHandle",it.toString())
})
)
}
My thinking is to include an if statement that almost does a "peek" of the second call before proceeding, but I'm not sure how to go about it. This is what I came up with so far to give an idea of my thinking:
fun getAllDynamicUtterances(module: String) {
var uttList: ArrayList<DynamicUtterance>? = ArrayList()
rxSubs?.add(
repository.getDynamicUtterances(module).map{res ->
res.uttSets.forEach {utt ->
utt.module = res.module!!
utt.transferInputValues()
utt.generateDefaultFlatTree()
if (doesNotReturnError(utt)){ // <- add this
uttList?.add(utt)
insertDynamicUtterance(utt)
repository.updateDynamicUtteranceView(utt).blockingForEach {
utt.assignSelectionStrings(it)
repository.storeDynamicUttPieces(utt.inputUttPieces)
utt.uttLinearisations = it.linearisations
updateDynamicUtterance(utt)
}
}
}
}.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()).subscribe ({
allDynamicUtterances?.postValue(uttList)
},{
Log.e("errorHandle",it.toString())
})
)
}
and then adding this function, or a function that performs what it is im trying to achieve in any case.
private fun doesNotReturnError(utt: DynamicUtterance): Boolean{
rxSubs?.add(
repository.updateDynamicUtteranceView(utt).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
//cant put a return here :(
}, {
Timber.e(it)
})
)
//returning over here will return nothing wont it?
}
I welcome comments on how to improve my getAllDynamicUtterances function
Sounds like a job for flatMap instead of map.
repository.getDynamicUtterances(module).flatMap{res ->
res.uttSets.forEach {utt ->
utt.module = res.module!!
utt.transferInputValues()
utt.generateDefaultFlatTree()
return if (doesNotReturnError(utt)){ // <- add this
uttList?.add(utt)
insertDynamicUtterance(utt)
repository.updateDynamicUtteranceView(utt).doOnNext {
utt.assignSelectionStrings(it)
repository.storeDynamicUttPieces(utt.inputUttPieces)
utt.uttLinearisations = it.linearisations
updateDynamicUtterance(utt)
}
} else {
Observable.error(utt.getError())
}
}

Retrofit + Debouce on EditText is causing an InterruptedIOException

I currently have an EditText for the user to enter a search. I'm trying to use RxJava with debounce to only search every so often, instead of each character. However, I'm getting an InterruptedIOException while I'm testing, which kills the stream.
private val subject = BehaviorSubject.create<String>()
init {
configureAutoComplete()
}
private fun configureAutoComplete() {
subject.debounce(200, TimeUnit.MILLISECONDS)
.flatMap {
getSearchResults(query = it)
}
.subscribe({ result ->
handleResult(result)
}, { t: Throwable? ->
Logger.e(t, "Failed to search")
})
}
fun getSearchResults(query: String): Observable<List<MyObject>> {
val service = NetworkService.create() // get retrofit service
return service.search(query)
}
fun search(text: String) {
subject.onNext(text)
}
As you can see, I'm creating a BehaviorSubject, and within init I'm setting it up with debounce.
getSearchResult returns an Observable and does my network request.
But as I'm testing, if I type at a specific rate ( usually quick-ish, like typing another character while the request is ongoing ) it'll throw an Exception.
Failed to search : java.io.InterruptedIOException
at okhttp3.internal.http2.Http2Stream.waitForIo(Http2Stream.java:579)
at okhttp3.internal.http2.Http2Stream.takeResponseHeaders(Http2Stream.java:143)
at okhttp3.internal.http2.Http2Codec.readResponseHeaders(Http2Codec.java:125)
I was looking at this, https://stackoverflow.com/a/47276430/3106174, and it seems like I'm doing everything correctly.
After more testing, I realized that the network request was on the main thread.
You can test this by replacing your network call with Observerable.create{ ... } and throwing a Thread.sleep(1000) inside.
I was following this tutorial, https://proandroiddev.com/building-an-autocompleting-edittext-using-rxjava-f69c5c3f5a40, and one of the comments mention this issue.
"But I think one thing is misleading in your code snippet, and it’s
that subjects aren’t thread safe. And the thread that your code will
run on will be the thread that you emitting on (in this case the main
thread). "
To solve this issue, you need to force it to run on Schedulers.io(). Make sure it's after the debounce or it won't work.
private fun configureAutoComplete() {
subject.debounce(200, TimeUnit.MILLISECONDS)
.observeOn(Schedulers.io()) // add this here
.distinctUntilChanged()
.switchMap {
getSearchResults(query = it)
}
.subscribe({ result ->
handleResult(result)
}, { t: Throwable? ->
Logger.e(t, "Failed to search")
})
}

Error Handle on UI thread when http request returns an error

I am using Fuel and Rxjava to make network calls. I have set my base URL to localhost, which at isn't serving anything. I want to be able to handle network errors so I can show some sort of error message on the UI to the user.
Here is an example of my GET request
fun getRandom(take: Int, responseHandler: (result: WikiResult) -> Unit?) {
Urls.getRandomURl(take)
.httpGet()
.timeout(timeout)
.timeoutRead(readTimeout)
.rx_object(WikipediaDataDeserializer())
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ result ->
val statusCode = result.component2()?.response?.statusCode
when(statusCode) {
-1 -> e(statusCode.toString(), result.component2()?.cause.toString())
else -> {
val (data, _) = result
responseHandler.invoke(data as WikiResult)
}
}
}, {
error -> e(error.cause.toString())
})
}
And on my fragment I am calling the above function in a async task
private fun getRandomArticles() {
refresher?.isRefreshing = true
wikiManager?.getRandom(15, { wikiResult ->
adapter.currentResults.clear()
adapter.currentResults.addAll(wikiResult.query!!.pages)
onUiThread {
adapter.notifyDataSetChanged()
refresher?.isRefreshing = false
}
})
}
private fun reportException(e: Throwable) {
refresher?.isRefreshing = false
val builder = AlertDialog.Builder(activity)
builder.setMessage(e.message).setTitle("Error")
val dialog = builder.create()
dialog.show()
}
So I get a network error java.net.ConnectException: Failed to connect to localhost/127.0.0.1:80
I want to be able to get this on my fragment and display an error on the fragment. Not sure what the best approach is for this.
You can see the full project code on here
https://github.com/limpep/android-kotlin-wikipedia
under branch feature/rxjava
any help would be much appreciated
It is hard to say how to write good code for this in your structure because your code is not very clear in its separation and it is not necessary to use AsyncTask and runOnUIThread when you are already using .subscribeOn() and .observeOn() on your observable.
Maybe this would be a better basis for structure:
fun getRandom(take: Int): Single<WikiResult> {
return Urls.getRandomURl(take)
.httpGet()
.timeout(timeout)
.timeoutRead(readTimeout)
.rx_object(WikipediaDataDeserializer())
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.map{ if(it.component2()!=null) throw it.component2() else it.component1() as WikiResult }
}
private fun getRandomArticles() {
refresher?.isRefreshing = true
getRandom().doOnCompleted{refresher?.isRefreshing = false}
.subscribe(this::handleResponse,this::reportException)
}
private fun handleResponse(wikiResult:WikiResult){
adapter.currentResults.clear()
adapter.currentResults.addAll(wikiResult.query!!.pages)
adapter.notifyDataSetChanged()
}

RxJava 2 Maybe with Observable

I have a simple case i'm trying to do with rxJava 2 but i'm stuck.
I have a locationProvider which returns a Maybe (some model with LatLng). Each time a user clicks on the button, I want to get his current location and save the location to the db.
Just to be clear,
The RxView.clicks(save_btn) should be on the MainThread, the location and db save should be on an async thread, and response should come back to mainThread.
How should I do this?
addDisposeable(RxView.clicks(save_btn)
.flatMap { locationProvider.getLastKnowLocation().toObservable() }
.map {
val place = Place(UUID.randomUUID().toString(), "test-address", it.latLng)
db.placeDao().insertAll(place)
}
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
Toast.makeText(this#AddPlaceActivity, "saved", Toast.LENGTH_SHORT).show()
}, { throwable -> Timber.e(throwable) }))
I've also tried to simplify it to this:
RxView.clicks(save_btn).share()
.flatMap { locationProvider.getLastKnowLocation().toObservable() }
.subscribe({
val place = Place(UUID.randomUUID().toString(), "test-address", it.latLng)
db.placeDao().insertAll(place)
Timber.d("place-saved")
}, { throwable -> Timber.e(throwable) }))
But this code only execute for the first click and doesn't continue after.
Is it due to the Maybe.toObservable()? how should it be resolved?
Thanks for your help.
This is the locationProvider's code, i'm using RxLocation:
fun getLastKnowLocation(): Maybe<LocationData> {
return rxLocation.location()
.lastLocation()
.map {
val address = geocoder.getFromLocation(it.latitude, it.longitude, 1).first()
val latlng = LatLng(it.latitude, it.longitude)
LocationData(latlng, address)
}
}
Unfortunately,the user who set me in the correct path deleted his answer :(
This is what I ended up with
addDisposeable(
RxView.clicks(save_btn).share()
.observeOn(Schedulers.computation())
.withLatestFrom(locationProvider.getLastKnowLocation().toObservable(), BiFunction { _:Any, l:LocationData -> l })
.map {
val place = Place(UUID.randomUUID().toString(), "test-address", it.latLng)
db.placeDao().insertAll(place)
}
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
Timber.d("place-saved")
}, { throwable -> Timber.e(throwable) }))
I couldn't find a nicer way for the 'BiFunction' part, if anyone has something nicer instead.
Also note that you need to call observeOn twice for the thread switching part.

Categories

Resources