observable onComplete not called when nested observable calls are made - android

For a nested observable, the nested observable's onComplete is not called when the same code is written in Kotlin.
The nested observable onNext gets called, but does not call onComplete and the call gets completed.
There is no exception thrown at either of the .subscribe calls.
Is there any equivalent to Coroutine code
val listItem = async {
...
}
val data = listItem.await()
output sequence for the following bit of code is:
// observable 1 completed
// observable 2 completed
getListObservable
.subscribeOn(Schedulers.io())
.observeOn(AndroidScheduler.mainThread())
.subscribe(
{
listItem ->
getListItemParams(listItem.id)
.subscribeOn(Schedulers.io())
.observeOn(AndroidScheduler.mainThread())
.subscribe(
{ paramList: List<BaseObservable?> ->
},
{ throwable: Throwable ->
},
{
// observable 2 completed
},
Disposable? ->
}
)
},
throwable: Throwable? ->
},
{
// observable 1 completed
},
Disposable? ->
}
)
I have tried wrapping the nested called in a CompositeDisposable, but with same results.
update:
getListItemParams(listItem.id) is a call to Room db query which returns an Onservable to subscribe to.
#Query("SELECT * FROM my_table WHERE id = :id")
fun getListItemParams(id:Int): Observable<List<DataPoint>>

Related

Room RxJava observable triggered multiple times on insert

I'm having a weird problem with my repository implementation. Every time I call my function that's supposed to get data from the database and update the database with a network call, I receive multiple results from my database observer.
override fun getApplianceControls(
serialNumber: SerialNumber
): Flowable<ApplianceControlState> {
val subject = BehaviorProcessor.create<ApplianceControlState>()
controlsDao.get(serialNumber.serial)
.map { controls ->
ApplianceControlState.Loaded(controls.toDomainModel())
}
.subscribe(subject)
controlApi.getApplianceControls(serialNumber.serial)
.flatMapObservable<ApplianceControlState> { response ->
val entities = response.toEntity(serialNumber)
// Store the fetched controls on the database.
controlsDao.insert(entities).andThen(
// Return an empty observable because the db will take care of emitting latest values.
Observable.create { }
)
}
.onErrorResumeNext { error: Throwable ->
Observable.create { emitter -> emitter.onNext(ApplianceControlState.Error(error)) }
}
.subscribeOn(backgroundScheduler)
.subscribe()
return subject.distinctUntilChanged()
}
#Dao
interface ApplianceControlsDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(controls: List<TemperatureControlEntity>): Completable
#Query("SELECT * FROM control_temperature WHERE serial = :serial")
fun get(serial: String): Flowable<List<TemperatureControlEntity>>
}
Basically, if I call getApplianceControls once, I get desired result. Then I call again, with another serial number, which is empty and I get the empty array. But then I call a third time, but with the same serial number as the first time and I get a mix of correct results and empty array after the insert call is made.
Like this:
1st call, to serial number "123" -> Loaded([control1, control2, control3])
2nd call, to serial number "000" -> Loaded([])
3rd call, to serial number "123" -> Loaded([control1, control2, control3]), Loaded([]), Loaded([control1, control2, control3])
If I remove the db insert from the api response, it works fine. Everything weird occurs after insert is called.
Edit: getApplianceControls() is called from the ViewModel.
fun loadApplianceControls(serialNumber: SerialNumber) {
Log.i("Loading appliance controls")
applianceControlRepository.getApplianceControls(serialNumber)
.subscribeOn(backgroundScheduler)
.observeOn(mainScheduler)
.subscribeBy(
onError = { error ->
Log.e("Error $error")
},
onNext = { controlState ->
_controlsLiveData.value = controlState
}
).addTo(disposeBag)
}
As i mention in comment you have 2 subscriptions that are not unsubscribed anywhere, it could cause memory leak (it doesn't dispose when subject is disposed), also with such implementation you ignore API errors.
i'd try to change it to:
override fun getApplianceControls(serialNumber: SerialNumber): Flowable<ApplianceControlState> {
val dbObservable = controlsDao.get(serialNumber.serial)
.map { controls ->
ApplianceControlState.Loaded(controls.toDomainModel())
}
val apiObservable = controlApi.getApplianceControls(serialNumber.serial)
.map { response ->
val entities = response.toEntity(serialNumber)
// Store the fetched controls on the database.
controlsDao.insert(entities).andThen( Unit )
}
.toObservable()
.startWith(Unit)
return Observables.combineLatest(dbObservable, apiObservable) { dbData, _ -> dbData }
// apiObservable emits are ignored, but it will by subscribed with dbObservable and Errors are not ignored
.onErrorResumeNext { error: Throwable ->
Observable.create { emitter -> emitter.onNext(ApplianceControlState.Error(error)) }
}
.subscribeOn(backgroundScheduler)
//observeOn main Thread
.distinctUntilChanged()
}
I'm not sure if it solves the original issue. But if so - the issue is in flatMapObservable
ALSO would be useful to see controlApi.getApplianceControls() implementation.

How to add the body of the subscribe method

In the below code, I am trying to add the body for the .subscribe(). I tried to add the lambda notation but it never worked. Would you please tell me how to implement the .subscribe() method?
Given that, the setupCommRequestService() returns Single<..>
code:
setupCommRequestService()?.
flatMap {
it.getAllPhotos()
.map {
Observable.fromIterable(it)
.map {
it
}
}
.toSortedList()
}
?.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(
)
There are 4 implementations for subscribe method according Single documentation. In a simple approach, you should implement a strategy for both onSucess and onError. therefor you should use the subscribe method either by passing a BiConsumer or 2 Consumer one for onSucess case and one for onError.
using BiConsumer in lambda:
val disposable = Single.just(1)
.subscribe { success, failure ->
/* whichever is not null */
}
or using 2 Consumer in lambda:
val disposable = Single.just(1)
.subscribe({ success ->
/* success */
}, { failure ->
/* failure */
})

Kotlin extension function on subsbcribing to RxJava's Flowable data

I want to write a function that automatically subsbcribes to RxJava's Flowable<T> and get the resulting data. This data will then be passed as an argument to another method that does the processing. I am struggling with Kotlin's extension function syntax and generics.
I want to convert this call:
val scheduler = Schedulers.newThread()
disposable.add(
viewModel.getExams().subscribeOn(scheduler)
.observeOn(scheduler)
.subscribe({ exams ->
exams.forEach {
getSubjectOfExam(it, Schedulers.newThread())
}
}, { error ->
Log.e(
"OverviewFragment",
"Unable to fetch list, $error"
)
})
)
which is very lengthy in my Activity code, to a method that returns the data that I want to process.
In this case I'd like a list of exams (List<Exam>) passed into the argument of getSubjectOfExam(), which is the method for the list processing.
My function so far, which compiles but does not work at all:
/**
* General subscription of items in a Flowable list
* #param f method to be executed when list is loaded
* #param scheduler scheduling units
*/
private fun Flowable<out List<Any>>.listSubscribe(
f: (List<Any>) -> Unit,
scheduler: Scheduler
) {
disposable.add(
this.subscribeOn(scheduler)
.observeOn(scheduler)
.subscribe({
f(it)
}, { error ->
Log.e(
"OverviewFragment",
"Unable to fetch list, $error"
)
})
)
}
it will be called like so:
viewModel.getExams().listSubscribe({ resultData ->
resultData.forEach {
val exam = it as Exam
getSubjectOfExam(exam, Schedulers.newThread())
}
}, Schedulers.newThread())
So yeah, I tried to make an extension function and passing a function as one of its arguments (called a higher-order function I believe).
With my method, the getSubjectOfExam doesn't get called at all. Is there something I'm missing?
I'll be subscribing to Flowable's all the time in my Activity so this function will really help me.
I tried your code and it seems it is working okay. Is there any chance that viewModel.getExams() or getSubjectOfExam() is not working?
Also I could suggest few optimizations:
protected fun <T> Flowable<out List<T>>.listSubscribe(
f: (List<T>) -> Unit,
scheduler: Scheduler
) {
disposable.add(
this.subscribeOn(scheduler)
.observeOn(scheduler)
.subscribe(f, { error ->
Log.e(
"OverviewFragment",
"Unable to fetch list, $error"
)
})
)
}
Then you won't need type conversion:
viewModel.getExams().listSubscribe({ resultData ->
resultData.forEach {
getSubjectOfExam(exam, Schedulers.newThread())
}
}, Schedulers.newThread())
In fact you can replace List<T> with just T and make it work with any types. Also, observing and subscribing with same scheduler doesn't make a lot of sense to me. I think you can remove.observeOn completely and the code will still observe on the same scheduler you put in .subscribeOn

RxJava parallel emit once

i try to get an array of flowable get executed in parallel with FlowableFromArray
and emit the FlowableFromArray when all flowables are done.
But I'm missing the method to emit on last/latest.
I could only manage to make it work to emit onNext #see below
val results = FlowableFromArray(flowableArray).parallel()
.runOn(Schedulers.io())
.sequential()
.zipWith(r, BiFunction { t1: Flowable<String>, t2: Int
->
t1
.subscribeOn(Schedulers.io())
.map { i -> parseYoutubeTrack(i) }
.observeOn(AndroidSchedulers.mainThread())
.subscribe { next -> TLog.i(TAG, "_NEXT_ ${next.videoId}") }
})
.subscribe()

RxAndroid Re-Subscribe to Observable onError and onComplete

My Question is probably more of the conceptual nature.
I get that by the Observable contract my Observable will not emit any more items after onComplete or onError is called.
But I'm using the RxBindings for Android and therefore it's not "my Observable" but the click on a Button that emits items.
fun observeForgotPasswordButton(): Disposable {
return view.observeForgotPasswordButton()
.flatMap {
authService.forgotPassword(email).toObservable<Any>()
}
.subscribe({
// on next
Timber.d("fun: onNext:")
}, { error ->
// on error
Timber.e(error, "fun: onError")
}, {
// onComplete
Timber.d("fun: onComplete")
})
}
observeForgotPasswordButton() returns an Observable
fun observeForgotPasswordButton(): Observable<Any> = RxView.clicks(b_forgot_password)
The problem is that authService.forgotPassword(email) is a Completable and it will call either onComplete or onError both of which lead to the fact that I cannot reuse the button anymore since the subscription ended.
Is there a way to circumvent this behavior?
Because in an error occurs I would like to be able to retry.
Also I would like it to be possible to send more then one password forgotten emails.
You can use the retry() and repeat() operators to automatically resubscribe to the original Observable (or Completable).
fun observeForgotPasswordButton(): Disposable {
return view.observeForgotPasswordButton()
.flatMap {
authService.forgotPassword(email).toObservable<Any>()
}
.repeat() // automatically resubscribe on completion
.retry() // automatically resubscribe on error
.subscribe({
// on next
Timber.d("fun: onNext:")
}, { error ->
// on error
Timber.e(error, "fun: onError")
}, {
// onComplete
Timber.d("fun: onComplete")
})
}

Categories

Resources