I have problem. Solution can be easy but my head is so overheat...
I want to call method ONCE after forEach loop will finish job.
Thanks for any example solution!
override fun saveWorkers(workers: ArrayList<Worker>): Single<Boolean> {
LogMgr.d(TAG, "saveWorkers() : $workers")
// remove old workers for current Event Planner and save new
workers.forEach {
deleteOldWorkers(it.event_planner_id!!)
.subscribeOn(getSubscriptionSchedulerForSave())
.subscribe({ status ->
}, { error ->
})
}
return Single.create({ emitter ->
RXModelAdapter.from(Worker::class.java)
.saveAll(workers)
.subscribeOn(getSubscriptionSchedulerForSave())
.subscribe({
LogMgr.d(TAG, "saveWorkers() onComplete")
emitter.onSuccess(true)
}, {
LogMgr.e(TAG, "saveWorkers() onError ", it)
emitter.onError(it)
})
})
}
Proposed sulution
Observable.merge(workers.map {
deleteOldWorkers(it.event_planner_id!!)
})
Observable.merge(workers.map { deleteOldWorkers(it.event_planner_id!!) }).doOnComplete() perhaps?
Related
I have asynchronous functions with firebase and other APIs that depend on each other. So, to start task B has to finish task A.
The async functions are retuning MyResult which can be a success or failure. Now I’m doing it in that way
when(val resullt1 = function1UseCase.getresult1()){
is MyResult.Success ->{
when(val result2 = function2UseCase.getResult2()){
is MyResult.Succes ->{
//Do something or call another async function
}
is MyResult.Failure ->{
//Do something or call another async function
}
}
}
is MyResult.Failure ->{
//Do something or call another async function
}
}
Is there a better way to do it? Because when I have more nested tasks the code doesn’t look very well.
Thanks!
You can create a simple extension. Something like that
inline fun <F, R> MyResult<F>.then(function: (F) -> MyResult<R>) = when (this) {
is MyResult.Succes -> {
try {
function(result)
} catch (throwable: Throwable) {
MyResult.Failure(your_error_handling_here)
}
}
is MyResult.Failure -> this
}
Annnd then it will be like that
when(val result = function1UseCase.getresult1().then { function2UseCase.getResult2() }) {
is MyResult.Success -> {
}
is MyResult.Failure -> {
//Do something or call another async function
}
}
You can slightly modify this if you need to have both results at the very end ;)
I have a problem with kotlin observables, I have searched through internet and stackoverflow but I think I am missing something in concepts. I have dashboardRepository which has method called getCallsCountForWeek, this basically returns flowable list for last 7 days and now I need to iterate through all flowables and then update my graph with count of calls user made for that day. Here is my code
fun getCallsCountForWeek(calendar: Calendar) : List<Flowable<Float>> {
val result = ArrayList<Flowable<Float>>()
for(index in 0..6) {
calendar.add(Calendar.DAY_OF_MONTH, -index)
result.add(dashbordDao.getCallsCountForDay(customSharedPreferences.getUser()?.id!!, CustomDateTimeUtil.getStartOfDay(calendar), CustomDateTimeUtil.getEndOfDay(calendar)).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()))
}
return result
}
Observable.fromArray(dashboardRepository
.getCallsCountForWeek(calendar). map {
items -> kotlin.run {
items.forEach {
it.subscribe({
Log.e("Result", " Count: " + it)
},{
Log.e("Error", "" + it)
})
}
}
}.doOnComplete {
//We will do this when it is completed
Log.e("Result", "Completed")
}.doFinally {
Log.e("Result", "Finally")
}.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe()
The problem is that doFinally and doOnComplete are called before map completes its iteration through all the flowables. I tried to use .zip for flowables but apparently could not make it work too.
According to other posts on stack overflow, doOnComplete is called when subscription is successful but I want that to happen after everything is done inside .map.
You should use flatMap or flatMapIterable instead of map and have only one subscribe call
Observable.fromArray(dashboardRepository
.getCallsCountForWeek(calendar)
.flatMapIterable { it } // iterate over list
.flatMap { it } // use flowables from list
.doOnNext { /* do something with every item */ }
.doOnComplete {
//We will do this when it is completed
Log.e("Result", "Completed")
}
.doFinally {
Log.e("Result", "Finally")
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.ignoreElements () // if you already handled everything in the doOnNext
.subscribe()
After looking into answer from Eugene Popovich. I was pointed into right direction and then I did the following and it worked.
So, first thing, I modified my function to return list of Single Observable instead of Flowable as that any who made ore sense. Once done I did following as suggested by Eugene but just using flatMapSingle instead of flatMap.
Observable.fromArray(dashboardRepository.getCallsCountForWeek(calendar))
.flatMapIterable { it } // iterate over list
.flatMapSingle {
it
}
.doOnNext {
barEtries.add( BarEntry(index++, it))
}
.doOnComplete {
//We will do this when it is completed
Log.e("Result", "Completed "+barEtries)
setBarChartData()
}
.doFinally {
Log.e("Result", "Finally")
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.ignoreElements () // if you already handled everything in the doOnNext
.subscribe()
The change in getCallsCountForWeek was as below, basically just used single instead of Flowable because it made more sense and flatMapSingle provided out of the box resolution without calling even subscribe.
fun getCallsCountForWeek(calendar: Calendar) : ArrayList<Single<Float>> {
val result = ArrayList<Single<Float>>()
for(index in 0..6) {
calendar.add(Calendar.DAY_OF_MONTH, -index)
result.add(dashbordDao.getCallsCountForDay(customSharedPreferences.getUser()?.id!!, CustomDateTimeUtil.getStartOfDay(calendar), CustomDateTimeUtil.getEndOfDay(calendar)).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()))
}
return result
}
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
})
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 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.