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.
Related
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
})
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())
}
}
This is what I want:
Check if I have data about products in database.
If I have data I run Single to get data from DB.
If not I run Single for get data from backend
If I get response I want to save data in DB using Completable.
After saving data I want to map values from step 2 or 3 to view model
In result I want to send data to activity.
This is what I have now:
checkProductsInDBUseCase.run()
.flatMap {
if (it) {
getProductsFromDBUseCase.run()
} else {
getProductsUseCase.run(3)
}
}.map {
it.products.map { item -> item.toViewModel() }
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeBy(
onSuccess = {
view.showBikes(it)
},
onError = {
view.showBikesError(it.message.toString())
}
).addTo(disposables)
Between flat map and map I need to run saveDataUseCase(it), but I don't know how to pass itfrom completable to map. Any ideas?
If your saveDataUseCase() is Completable then you can do this
checkProductsInDBUseCase.run()
.flatMap {
if (it) {
getProductsFromDBUseCase.run()
} else {
getProductsUseCase.run(3)
}
}
.faltMap {
saveDataUseCase(it).toSingleDefault(it)
}
.map {
it.products.map { item -> item.toViewModel() }
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeBy(
onSuccess = {
view.showBikes(it)
},
onError = {
view.showBikesError(it.message.toString())
}
).addTo(disposables)
But if you change return type of saveDataUseCase() to Unit, you can use Fred's answer. It would be better
Here I'd use doOnSuccess. This seems ideal especially because you're creating a side effect, which we usually use the doOnXXX methods for.
checkProductsInDBUseCase.run()
.flatMap {
if (it) {
getProductsFromDBUseCase.run()
} else {
getProductsUseCase.run(3)
}
}
.doOnSuccess {
saveDataUseCase(it)
}
.map {
it.products.map { item -> item.toViewModel() }
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeBy(
onSuccess = {
view.showBikes(it)
},
onError = {
view.showBikesError(it.message.toString())
}
).addTo(disposables)
The method will not change the result of the flatMap so you will still get the correct object inside the map function.
I'm trying to do a simple search UI, where the text change triggers a search in the service and that gets mapped to a ViewState. It would seem easy, but the following code doesn't work:
queryText.filter { it.length > 3 }
.switchMap { service.search(it) }
.onErrorReturn { SearchResponse(null, it.message) }
.map { SearchViewState(items = it.items, error = it.error) }
.startWith { SearchViewState(loading = true) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe { viewState.onNext(it) }
I've no idea what I did wrong, but through debugging I can see that the stream throws a NetworkOnMainThreadException and then terminates so new events are no longer processed.
What is the correct way to do this?
I assume queryText is the source of textchanges which happen on the main thread. Therefore subscribeOn has no effect on it. You should apply subscribeOn to the actual network call:
queryText.filter { it.length > 3 }
.switchMap {
service.search(it)
.subscribeOn(Schedulers.io())
.onErrorReturn { SearchResponse(null, it.message) }
.map { SearchViewState(items = it.items, error = it.error) }
.startWith ( SearchViewState(loading = true) )
}
.observeOn(AndroidSchedulers.mainThread())
.subscribe { viewState.onNext(it) }
In addition, I think you have to do the error recovery and state changes associated with the particular network call, otherwise a failure will stop the entire sequence.
I am a beginner with rxjava/rxkotlin/rxandroid.
I need to deal with three diferent async-calls in a sequence.
The problem is that the first step returns a Single<LocationResult>, the second a Completableand the third again a Completable.
(Single -> Completable -> Completable)
The problem is now that the last Completable depends on the data of the first Single
My current solution:
I think this is a bad solution, but I don't know how to do this right.
val ft = FenceTransaction(applicationContext, apiClient)
stream
.flatMap { locationResult ->
ft.removeAll()
return#flatMap ft.commit().toSingle({ return#toSingle locationResult })
}
.flatMapCompletable {
ft.recycle()
ft.setZone(it.location.longitude, it.location.latitude, ZONE_RADIUS)
val dots = DotFilter().getFilteredDots()
for (dot in dots) {
ft.addDot(dot)
}
return#flatMapCompletable ft.commit()
}
.subscribeBy(
onComplete = {
"transaction complete".logi(this)
},
onError = {
"transaction error".logi(this)
})
Is this approch the correct way to do it?
And how should I dispose the Completeables?
Generally when should I dispose Observables?
No idea if you still have this issue but generally for Single->Completable->Completable you'd do:
val disposable = service.getSingleResult()
.flatMapCompletable { locationResult ->
callReturningCompletable(locationResult)
}
.andThen { finalCompletableCall() }
.subscribe({...}, {...})