I have a bottom bar with four tabs. Each tab is a Fragment. I want to stop any network calls when user moves to another Fragment so I'm adding all Observable calls to a CompositeSubscription and I unsubscribe from them in onDestroyView(). Next time user enters the screen all network calls fail (since I have unsubscribed) so I want to subscribe again.
I'm not sure how I am supposed to do this : somehow I have to re-subscribe when onResume()/onViewAttached() is called for the Fragment. I'm using RxJava 1.
Edit : I have checked similar questions and their answers mention cache and replay operators but I don't think that's the case cause they were asking to also get the previously emitted items, while I just want to be able to perform again any network calls.
This is how I'm subscribing to an Observable for a network call :
subscriptions.add(remoteDataSource.getFeedMore(localDataSource.getFirstStoredId())
.doOnNext(new Action1<FeedItemsRequestDetailsWrapper>() {
#Override
public void call(FeedItemsRequestDetailsWrapper wrapper) {
if (wrapper != null) {
localDataSource.saveFeed(wrapper.getFeedItemList());
localDataSource.saveServerState(wrapper.getFeedRequestDetails());
}
}
})
.subscribeOn(schedulerProvider.io())
.observeOn(schedulerProvider.mainThread())
.subscribe(new Action1<FeedItemsRequestDetailsWrapper>() {
#Override
public void call(FeedItemsRequestDetailsWrapper wrapper) {
// call to View to update
}
}));
And how I unsubscribe :
#Override
public void unsubscribe() {
if (subscriptions != null && subscriptions.hasSubscriptions()) {
subscriptions.unsubscribe();
}
}
Example Use Case : user enters Timeline screen, user clicks a button and a network call is executed ( modeled as an Observable in my Presenter class like the code I posted right above ). Then user exits this screen (
onDestroyView() is called and any subscriptions are unsubscribed). Some time later user enters Timeline screen again and clicks the button.
This is when I receive HTTP FAILED: java.io.IOException: Canceled cause I have unsubscribed and I want to re-subscribe again so I can execute the network call without errors.
Thanks
Update
If you call unsubscribe in CompositeSubscrition you can't add new subscriptions to it again. If you want to use the same composite instance again, then you need to call subscriptions.clear() or you can create a new instance when the fragment is initialized.
Prev
First things first, if you unsubscribe from any observable/stream/flowable etc. you gonna lose the any incoming data/events.
If you want to get the latest result of an subscription then obviously you should do it before unsubscribe happens.
The problem here is your subscriptions should not be dependant on any Fragment or Activity lifecycle unless it's totally finished/destroyed.
So if you know that you have long requests you should subscribe them on a android.app.Service.
Then you will face another problem communicating back and forth between Services and Fragments/Activities.
The easy solution on your case is you can create a BehaviourSubject in a singleton class (better to use Dagger to inject that model to both fragment and service). Then in your Service subscribe to your long running stream and publish the next events to that BehaviourSubject
BehaviourSubject saves the last emitted data. So next time you subscribe them in your newly created fragment it will start with the last emitted item.
Of course this answer just cover one use-case according to your needs you may need to do something else.
Related
I am reading LiveData source code, and for this method:
public void observe(#NonNull LifecycleOwner owner, #NonNull Observer<?super T> observer) { .. }
And this is part of its doc:
When data changes while the owner is not active, it will not receive
any updates. If it becomes active again, it will receive the last
available data automatically.
I was trying to figure out why LiveData can achieve this lifecycle-awareness. I read source code of setValue but still couldn't get it. Can anybody help me with the on the general idea?
Suppose you are not using live data and you are showing list of data with pagination concept . You as user have scroll down to view more and more data and application is calling apis to get data as you scroll . Now you have rotated your device so as developer we know that your activity will be recreated and user will be at initial stage again as all things were destroyed .. Well you can achieve this using onSaveInstance but you will have to code and manage yourself ..
So user will have to scroll again to view all data but imagine if you have some mechanism where you get your last updated data whenever activity lifecycle changes so you can easily set data again and allow user to use your app like nothing happened .. and here livedata concept come into picture with lifecycle awareness
Hope this answer will clear your doubts
Edit :-
To understand how they are managing lifecycle , you can visit this link
https://developer.android.com/topic/libraries/architecture/lifecycle#lc
here is source code method of LiveData class where you can see(at last line) how they are adding lifecycler owne to observer
Read the source code of LiveData.java (in lifecycle-livedata-core:2.2.0#aar) again, it seems clear to me now.
When adding an observer to LiveData via liveData.observe(lifecycleOwner, Observer { .. } ), the magic happens in the observe method.
In the observe method, it puts the lifecycleOwner and the Observer into a new object called LifecycleBoundObserver like this:
LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
// ... some other code
owner.getLifecycle().addObserver(wrapper);
Important to note and we can also tell it from the last statement: the LifecycleBoundObserver is an instance of LifecycleObserver, that means, it can be notified when the given lifecycleOwner gets state updated, the key lies in the onStateChanged method of LifecycleBoundObserver.
#Override
public void onStateChanged(#NonNull LifecycleOwner source,
#NonNull Lifecycle.Event event) {
if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
removeObserver(mObserver);
return;
}
activeStateChanged(shouldBeActive());
}
So:
If the lifecycleOwner (the Activity or Fragment) gets DESTROYED, it will remove the observer (the callback observer, not the LifecycleObserver), hence the observer won't be notified for new data once it's DESTROYED.
If it is not DESTROYED, it requires the lifecycleOwner in Active states (STARTED or RESUMED), this is restricted by the return value from the method shouldBeAlive(), and finally the new data gets delivered to observer callback in the method activeStateChanged.
void activeStateChanged(boolean newActive) {
if (newActive == mActive) {
return;
}
// immediately set active state, so we'd never dispatch anything to inactive
// owner
mActive = newActive;
boolean wasInactive = LiveData.this.mActiveCount == 0;
LiveData.this.mActiveCount += mActive ? 1 : -1;
if (wasInactive && mActive) {
onActive();
}
if (LiveData.this.mActiveCount == 0 && !mActive) {
onInactive();
}
if (mActive) {
dispatchingValue(this);
}
}
If the input param newActive is true, then finally it will reach the statement of dispatchingValue(this) - the last statement, and if it is false (i.e.: inactive states: PAUSED / STOPPED / DESTROYED), it won't call dispatchingValue(this), thus the observer callback won't be triggered.
If the Activity / Fragment goes back to foreground from background, it becomes Active again, then the LifecycleBoundObserver will be notified and the onStateChanged will be called again, and this time, when calling activeStateChanged(newActive), it passes true, therefore, dispatchingValue(this) will be called, and the latest data set via setValue or post will be picked up, that's the reason for explaining why the Activity can get the last emitted / latest value of LiveData.
I have this code in an activity SignInActivity:
signInButton.setOnClickListener{
val query: HashMap<String, String> = HashMap()
query["email"] = signInEmail.text.toString()
query["password"] = signInPassword.text.toString()
signInViewModel.getAuthToken(query)
signInViewModel.signInResponse.observe(this, {
response-> when(response){
is NetworkResult.Success ->{
response.data?.let { Toast.makeText(this, it.access, Toast.LENGTH_SHORT).show()}
}
is NetworkResult.Error ->{
Toast.makeText(this, response.message.toString(), Toast.LENGTH_SHORT).show()
}
is NetworkResult.Loading -> {
}
}
})
}
Let's suppose in the first try I wrote my password wrong and it only runs once, but then after that if I click it again it runs multiple time by creating multiple toasts in this example.
Like #gpunto says, you're adding a new Observer every click, so they're stacking up and each one fires when the LiveData updates.
But really, the observer doesn't have anything to do with the actual click anyway, it just receives updates to signInResponse and displays a thing. The click just calls getAuthToken with the current query. If doing that happens to cause a signInResponse update, then you have everything wired up to react to that event. But the Activity doesn't need to know how all that stuff works, or be written so one thing follows another.
That's a reactive pattern, where your UI is really just sending events (like getAuthToken when there's a click) and then reacting to other events so it can display them. By separating these things, you get a simple system that Just Works, and can react to updates no matter what caused them (e.g. a click, or restoring state) without having to write code to handle each case.
That said, this is a slightly tricky case because you have an event you want to consume. If you just set up that observer on signInResponse, it will fire every time you get a value for that LiveData. And that includes when the Activity is recreated (e.g. on rotation), observes the LiveData, and gets the current (last-set) value. Basically, if you show a Toast, the same Toast will appear every time the Activity is recreated. That would be fine for setting the current value on a TextView, but it's bad for a popup that should only appear once.
This is the current official recommendation for handling this situation. They're creating a UI state, which basically holds everything that needs to be displayed, including any popup messages (which acts like a queue, which is useful!). When the UI displays a message, it basically tells the ViewModel it's done so, and that handles removing the message from the state.
You could just implement this your own way, even if it's something simple like a clearResponse() function in your VM that clears the current value when you've seen it. It really depends on your app and what state you need to maintain. Here's some other examples from the Android devs - but like it says at the top, this advice is deprecated following the recommendations I linked earlier
My retrofit interface method is returning rx Single with the response
#GET("/data")
Single<Response<List<Foo>>> getData();
and in my Activity onStart() method i call getData() to populate the data and until that am showing a loading Progress it will dismiss on success or fail
getData().observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.doOnSubscribe(() -> showProgress("loading"))
.doOnSuccess(listResponse -> hideProgress())
.subscribe(new SingleSubscriber<Response<List<Foo>>>() {
#Override
public void onSuccess(Response<List<Cat>> response) {
if (response.isSuccessful()) setItems(response.body());
}
#Override
public void onError(Throwable error) {
hideProgress(error.getMessage());
}
})
the first time i start the Activity all goes well the Progress will show and on success or fail it dissmised
The problem is
whenever i start a new Activity via startActivity() and then back to my Activity onStart() is called again and my Retrofit call gets executed again (i did that to keep my data fresh every time i enter my Activity)
but what happens is the Progress is shown till ever, no Success, no Fail happen, i even logged the request i am making on the server and there is no request being requested "at my second time not the first time the Activity opened"
what could the problem be is it in the Retrofit or Rxjava
is there any approaches to do the fresh data thing every time the activity is started or poped from the stack
Are we till ever doomed with the Android activity lifecycle curse...
My sixth sense told me that it is a silly mistake or a misunderstand
i was using a CompositeSubscription to add all my calls to it and unsubscribe all of them safely in onStop() by calling CompositeSubscription.unsubscribe() to prevent resource leaks
what was happening is when i return to my activity i was adding the same subscription to the composite and it was unsubscribed immediately
the docs says :
public void unsubscribe()
Unsubscribes itself and all inner subscriptions.
After call of this method, new Subscriptions added to CompositeSubscription >will be unsubscribed immediately.
and i resolve the problem by calling clear() instead
public void clear()
Unsubscribes any subscriptions that are currently part of this >CompositeSubscription and remove them from the CompositeSubscription so that >the CompositeSubscription is empty and able to manage new subscriptions.
then you are able to add the same subscription again and being called as it normally behave
24 hours lost for that. -_- .
You are not stopping your activity when you return to your activity you need to override your onResume() method. Instead of onCreate() or onStart() call your getData method in onResume(). This will do the job.
I'm trying to migrate an AsyncTask that sends messages to the server, to use RxJava. Roughly, the task does the following:
1) Creates a message that will be sent (persists to the database)
2) Shows the message to the user (state 'sending')
3) Sends the message to the server (code snippet below)
4) Marks the message as sent or failed (persists to the database)
5) Updates the UI
I've created the required Rx chain which partially looks like this:
public Observable<Message> sendMessage(Message message) {
return mApiClient.sendMessage(message)
.doOnNext(sentMessage -> mDatabase.synchroniseMessage(sentMessage))
.doOnError(e -> {
message.setState(FAILED);
mDatabase.synchroniseMessage(message));
})
.onErrorReturn(e -> Observable.just(message));
When I subscribe to the above, I get a Disposable. Normally I'd add it to the CompositeDisposable object and clear that object then the user has moved to a different view (i.e. fragment). However, in this case, I need to keep running this task to make sure the local database is updated with the task results accordingly.
What would be the most appropriate way to handle this situation? I could simply not add the Disposable into my CompositeDisposable object and therefore it wouldn't be unsubscribed from, but it feels like it could cause issues.
P.S. Showing updates to the user is handled through observing the data in an SQLite table. These events are triggered by the synchroniseMessage method. This is a different subscription which I will simply unsubscribe from, so it's not part of the problem.
One disposes of Disposable as soon as he is no longer interested in it.
In your case you are still interested in the stream regardless user navigates to another screen or no, which means you cannot unsubscribe from it. Which means you cannot add it to CompositeDisposable.
This will result in a situation, when your Activity cannot be garbage collected, because of a implicit reference to it from your Subscription, hence you are creating a memory leak situation.
If you have such a use case, I think you have to perform that request on a component, which will be activity lifecycle independent, like Service.
I'd like to create an observable in a singleton class that manages state (i.e. it stores an auth token). I'd like my android app/activity to subscribe to an observable that will emit an update every time the state (auth token) is updated. How do I do this? All examples I've seen show how you can create a self contained observable that completes immediately or after subscription.
Thanks for your help!
You need a BehaviorSubject.
BehaviorSubject<State> rxState = BehaviorSubject.create(initialState);
// update state
rxState.onNext(newState);
// observe current state and all changes after
rxState.subscribe(...);
If you want to set the state from multiple threads concurrently, you need this as the first line.
Subject<State, State> rxState = BehaviorSubject.create(initialState).toSerialized();