Trouble Resubscribing to observable - android

I'm following Kaushik Gopal's implementation of MVI with a ViewStates Observable and a ViewEffects Observable. Example can be seen here: https://github.com/kaushikgopal/movies-usf/blob/master/app/src/main/java/co/kaush/msusf/movies/MSMovieVm.kt
Relevant snippet:
init {
eventEmitter
.doOnNext { Timber.d("[Event]: $it") }
.eventToResult()
.doOnNext { Timber.d("[Result]: $it") }
.share()
.also { result ->
viewStates = result
.resultToViewState()
.doOnNext { Timber.d("[ViewState]: $it") }
.replay(1)
.autoConnect(1) { disposable = it }
.distinctUntilChanged()
viewEffects = result
.resultToViewEffect()
.doOnNext { Timber.d("[ViewEffect]: $it") }
}
}
The major difference is that I'm using Fragments instead of Activities. Everything works the first time around, but when I go from Fragment A to Fragment B, then back to Fragment A, the result observable triggers new ViewStates, but no new ViewEffects. Is there a special way I should be subscribing/unsubscribing or am I constructing the rx chain in a strange way? I currently subscribe to both the ViewStates and ViewEffects Observables in onViewCreated() and dispose in onViewDestroyed(). I've verified that both Observables are subscribed to in each case, it's just that when I come back to Fragment A, and input new ViewEvents, I get new ViewStates but not ViewEffects. Maybe it's a multi-casting issue?

Related

Can an AsyncSubject handle the SingleLiveEvent case in RxJava?

Problem
The issue with reactive programming patterns for one-time events is that they may be re-emitted to the subscriber after the initial one-time event has occurred.
For LiveData the SingleLiveEvent provides a solution using an EventObserver which may also be applied to Kotlin Flow.
Question
Can an AsyncSubject observable be created to handle the case of the SingleLiveEvent in RxJava? The main issue seems to be if there a way for an AsyncSubject to be manually "re-opened" to re-emit data after onComplete is called?
Potential solution
AsyncSubject seems like a potential solution for RxJava, without creating an EventObserver, as the documentation states that it will only publish it when the sequence is completed.
Implementation - Loading status sample
A loading boolean is emitted from the ViewModel method initFeed and view effect state to the view, a fragment in this case. The loading boolean works as expected on the initialization of the fragment and ViewModel sending true via onNext, and completing with onComplete on either a successful or erroneous attempt.
However, the attempt to re-emit a value fails when for example a swipe to refresh initiates the same initFeed method. It seems that onNext cannot be used after onComplete is called for the same object.
SomeViewEffect.kt
data class _FeedViewEffect(
val _isLoading: AsyncSubject<Boolean> = AsyncSubject.create(),
)
data class FeedViewEffect(private val _viewEffect: _FeedViewEffect) {
val isLoading: AsyncSubject<Boolean> = _viewEffect._isLoading
}
SomeViewModel.kt
private fun initFeed(toRetry: Boolean) {
val disposable = feedRepository.initFeed(pagedListBoundaryCallback(toRetry))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe { results ->
when (results.status) {
LOADING -> {
Log.v(LOG_TAG, "initFeed ${LOADING.name}")
_viewEffect._isLoading.onNext(true)
}
SUCCESS -> {
Log.v(LOG_TAG, "initFeed ${SUCCESS.name}")
_viewEffect._isLoading.onNext(false)
_viewEffect._isLoading.onComplete()
_viewState._feed.onNext(results.data)
}
ERROR -> {
Log.v(LOG_TAG, "initFeed ${ERROR.name}")
_viewEffect._isLoading.onNext(false)
_viewEffect._isLoading.onComplete()
_viewEffect._isError.onNext(true)
}
}
}
disposables.add(disposable)
}
SomeFragment.kt
private fun initViewEffects() {
val isLoadingDisposable = viewModel.viewEffect.isLoading
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnError { Log.v(LOG_TAG, "Error loading isLoading") }
.subscribe { isLoading ->
if (isLoading) progressBar.visibility = VISIBLE
else {
progressBar.visibility = GONE
swipeToRefresh.isRefreshing = false
}
}
compositeDisposable.addAll(isLoadingDisposable, isErrorDisposable)
}
It is not very clear why you need AsyncSubject which emits only last event. Did you try to use Behavior or Publish Processor for this situation?
Use an Event Wrapper
An AsyncSubject does not seem to be a suitable solution to handle one-time occurrence emissions from an Observable to a Subscriber. After onComplete is called an AsyncSubject can not "re-open" to emit future one-time events.
Using an event wrapper such as an Event, as outlined in LiveData with SnackBar, Navigation and other events (the SingleLiveEvent case) is the best approach.
FeedViewEffect.kt
data class _FeedViewEffect(
val _isLoading: BehaviorSubject<Event<Boolean>> = BehaviorSubject.create()
)
data class FeedViewEffect(private val _viewEffect: _FeedViewEffect) {
val isLoading: BehaviorSubject<Event<Boolean>> = _viewEffect._isLoading
}
FeedViewModel.kt
private fun initFeed(toRetry: Boolean) {
val disposable = feedRepository.initFeed(pagedListBoundaryCallback(toRetry))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe { results ->
when (results.status) {
LOADING -> _viewEffect._isLoading.onNext(Event(true))
SUCCESS -> _viewEffect._isLoading.onNext(Event(false))
ERROR -> _viewEffect._isLoading.onNext(Event(false))
}
}
disposables.add(disposable)
}
FeedFragment.kt
#ExperimentalCoroutinesApi
private fun initViewEffects() {
val isLoadingDisposable = viewModel.viewEffect.isLoading
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnError { Log.v(LOG_TAG, "Error loading isLoading") }
.subscribe { isLoading ->
if (isLoading.getContentIfNotHandled() == true) {
progressBar.visibility = VISIBLE
} else {
progressBar.visibility = GONE
swipeToRefresh.isRefreshing = false
}
}
compositeDisposable.addAll(isLoadingDisposable)
}

RxJava dispose Observable in subscribe

Are there any clear and smart solution to dispose observable from its subscribe method?
E.g. I have an Observable.interval that emits Long every second. In my subscribe method i want to check if 20 seconds gone than dismiss subscription.
val observable = Observable.interval(1000,TimeUnit.MILLISECONDS)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ num ->
if(num == 20.toLong())
{
observable.dispose()//I cant call dispose here because variable 'observable' is not avaliable yet
}
})
What is the easiest and right way to achieve this logic?
I found one simple solution. Just divide variable declaration and initialization in to two steps.
E.g.
var observable:Disposable? = null
observable = Observable.interval(1000,TimeUnit.MILLISECONDS)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ num ->
if(num == 20.toLong())
{
observable?.dispose()
}
})

How to get a callback when every observer of a hot like ConnectableObservable complete?

I am searching for a doOn... callback for a ConnectableObservable that is invoked when every observer terminates
val gatewayItems = viewModel.getGatewayItems(gateways!!)
.observeOn(Schedulers.io())
.take(1)
.publish()
.autoConnect(2)
gatewayItems.subscribe { sharedGateways -> sharedGatewaysAdapter.submitList(sharedGateways) }
gatewayItems.subscribe { sharedGateways -> privateGatewaysAdapter.submitList(privateGateways) }
I would like to get a callback to my multicasted hot observable when both of my observers signal a terminal event
I have tried to put doOnTerminate and doOnComplete operators on my parent multicasted observable but, it seems that these callbacks are invoked 2 times (one for each observer)
val gatewayItems = viewModel.getGatewayItems(gateways!!)
.observeOn(Schedulers.io())
.take(1)
.doOnComplete { ... }
.doOnTerminate { ... }
.publish()
.autoConnect(2)
Both .doOnComplete and .doOnTerminate work for me.
Edit: the chances are you might be attaching the do... operators in incorrect order. For example, neither of these doOnComplete will work:
val gatewayItems = viewModel.getGatewayItems(gateways!!)
.observeOn(Schedulers.io())
.doOnComplete { ... }
.take(1)
.publish()
.autoConnect(2)
.doOnComplete { ... }

Asynctask status && cancel equivalent in RxJava2 Observable?

I am trying to learn RxJava2, and converting my AsyncTasks to Observables.
I have the following piece of code that I am trying to convert.
if(asyncTask.getStatus() == AsyncTask.Status.RUNNING){
asyncTask.cancel();
}
asyncTask = new CustomTask();
asyncTask.execute(input);
I tried to recreate the following with Disposables.
Disposable currentTask;
PublishSubject input = PublishSubject.create();
For every input
if(currentTask != null) currentTask.dispose();
currentTask = input
.map(// Network calls
// returns CustomObject)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(result -> {
// do work with result
}, throwable -> Log.e(TAG, throwable.toString()));
However, currentTask is always null. Why? Is this the wrong way to do it??
You're using Disposable correctly but I can only assume you're messing up somewhere with the subject. Subjects in rx can be both publishers and subscribers... and subjects don't necessarily wait until subscribe(...) to start emitting items. For that reason, I wouldn't suggest replacing your AsyncTasks with any kind of Subject.
You can get similar, more deterministic behavior, doing this though:
Observable<CustomObject> networkObservable =
Observable.create(emitter ->
{
try {
CustomObject object = doNetworking();
emitter.onNext(object);
emitter.onComplete();
} catch (Exception e) {
emitter.onError(e);
}
}
);
if(currentTask != null) currentTask.dispose();
currentTask = networkObservable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
// this next subscribe is similar to AsyncTask.execute() as it starts the stream
.subscribe(result -> {
// do work with result
}, throwable -> Log.e(TAG, throwable.toString()));
Also, consider looking into SerialDisposable and you don't have to do those null/dispose checks
SerialDisposable serialDisposable = new SerialDisposable();
Atomically: set the next disposable on this container and dispose the previous one (if any) or dispose next if the container has been disposed.
Disposable disposable = networkObservable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(...);
serialDisposable.set(disposable); // this will call dispose on the previous task if it exists

How to chain "Single - Completeable - Completable" in rxkotlin?

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({...}, {...})

Categories

Resources