I have the following code in my Fragment, subscribing to my ViewModel's LiveData events.
viewModel.successfullyAddedEvent.observeEvent(this){
// do result handling by shared view model to the calling fragment
result.successfullyAddedEvent.postValue(Event(it))
findNavController().navigateUp()
}
viewModel.successfullyEditedEvent.observeEvent(this){
// do result handling by shared view model to the calling fragment
result.successfullyEditedEvent.postValue(Event(it))
findNavController().navigateUp()
}
viewModel.exitRequestedEvent.observeEvent(this){
when(it){
ExitReason.GetDetailsFailed -> {
Toast.makeText(context, R.string.details_load_error, LENGTH_SHORT).show()
}
ExitReason.UserCanceled -> { /* happy path */ }
}
findNavController().navigateUp()
}
I have three places in this code where I'm calling navigateUp and I'd like to unify the navigation a bit.
It seems like it would be a code improvement to emit an ExitRequestedEvent from each case (successful add, successful edit, error case, user cancellation) and remove calls to navigateUp from those events, but I'm not sure about guarantees on receipt order. That is, if I was to post values for a successfullyAddedEvent and then an exitRequestedEvent in my viewmodel like so:
// yay my thing happened successfully, emit events
successfullyAddedEvent.postValue(Event(contentAdded))
exitRequestedEvent.postValue(Event(ExitReason.AddSuccessful))
can I be guaranteed that I'll handle the add event (which sets the result on the shared result ViewModel) before I exit this fragment, so that the calling fragment always has a result?
According to the documentation, postValue just means posting the task to main thread if you are not on it, so unless you have setValue somewhere in your code as well, the order should be guaranteed.
If you want to absolutely guarantee the order (even though not necessary), you can use setValue() for that, but you have to make sure it happens on the main thread.
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
In my application I have an Activity that holds 3 Fragments. The very first time the Activity is created, Fragment 1 is displayed. Next, all fragment transactions will be executed after a network operation. For example: Fragment 1 has a button to make a request to the server and when the result is ready, Fragment 1 uses a listener to call a method defined inside the parent activity, to replace fragment 1 with fragment 2.
This works fine, except when the parent activity receives the callback after its state has been saved by onSaveInstanceState(). An IllegalStateException is thrown.
I've read some answers about this problem, for example this post and I understood why this exception happens thanks to this blog.
I also take an example that I found here to try to solve the problem. This post suggests to always check if the activity is running before call commit(). So I declared a Boolean variable in the parent activity and I put its value to false in onPause() and to true in onResume().
The parent activity callback called after network operations has been completed is something like this piece of Kotlin code, where next is the number of the replacing fragment:
private fun changeFragment(next:Int){
// get the instance of the next fragment
val currentFragment = createFragment(next)
// do other stuff here
if(isRunning){
// prepare a replace fragment transaction and then commit
ft.commit()
}else{
// store this transaction to be executed when the activity state become running
}
}
This code is working fine and now I'm not getting the Exception anymore, but my question is: it's possible that onSaveInstanceState() is called after I check if(isRunning) and before I call ft.commit(), so that the commit() happens after the activity state has been saved causing IllegalStateException again?
I'm not sure if onSaveInstanceState() could interrupt my changeFragment() method at any point in time. Is it possible?
If the possibility exists and my code may be interrupted between if(isRunning) and ft.commit(), what I can do?
It could be solved adding a try{}catch(){} block like this?:
if(isRunning){
try{
ft.commit()
}catch(ie:IllegalStateException){
// store the transaction and execute it when the activity become running
}
}else{
// store the transaction and execute it when the activity become running
}
Its a bit late but as of API 26+ we can use following to check if we need to do a normal commit or commitAllowingStateLoss().
getSupportFragmentManager().isStateSaved();
Are you storing anything when you're changing states?
If not, then you can try commitAllowingStateLoss().
onSaveInstanceState() would not be able to interrupt your method if your method is being called on the main (UI) thread.
Another approach that tends to make your life easier is to not use callbacks, but rather adopt a reactive pattern like MVVM. In that pattern, your Activity or Fragment subscribe to an observable when they are interested in e.g. network responses and unsubscribe typically in the onStop or onPause lifecycle callbacks so that your methods never get called after onSaveInstanceState. For a good starting place, check the official LiveData overview.
Suppose I have
Disposable disposable = signOutUser()
.subscribe((Response<ResponseBody> response) -> {
if (response.isSuccessful()) {
Intent intent = new Intent(view.getContext(), SignInUserActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NO_HISTORY);
view.getContext().startActivity(intent);
((FragmentActivity) view.getContext()).finish();
}
}, (Throwable ex) -> {
Log.e(TAG, "signOutUser: " + ex.getMessage());
});
where signOutUser() returns Single<Response<ResponseBody>>. When signOutUser() is successful, there is an Intent and the current activity is finished(). Otherwise, it fails, possibly due to network error, so there is no intent and the user stays on current activity.
Since this isn't something to observe (it's a one time event, success or fail), and IF the user successfully logs out, then onDestroy will be called which calls compositeDisposable.clear() which clears all the disposables. So then I'm adding a Disposable and immediately that Disposable is being disposed or cleared.
My question is, do I event need to use Composite Disposable? Do I immediately call disposable.dispose() after subscribe? Do I set anything to null? Do I not use Single?
Do I event need to use Composite Disposable?
Yes, you should always use composite disposable (or normal Disposable), and unsubscribe from it when the time comes (onDestroy/onStop whathere you need). The reason for it is that the network call may be finished after you have closed activity, which will result in memory leaks or even crashes (because context will be null).
Do I immediately call disposable.dispose() after subscribe?
No, because this would result in the call to never return a result. If you dispose immediately after calling subscribe, you will never get a response from it. Read about what happens after you dispose an observable.
Do I set anything to null?
No need to. If your single has finished, you don't have to do anything about it. And there won't be any problems that it is still in CompositeDisposable (even if you call dispose on it). Actually, after the Single is finished, RxJava will dispose the observable itself to safe some memory.
Do I not use Single?
Yes, this is perfect situation to use it. You want to perform a single request, no need to use Observable.
Yes, you should use a disposable. Consider the case when the response is received from your API call, but the context is gone for whatever reason. Then, all your code where you are getting the context and calling methods on it would cause an NPE. Disposing properly of this Single would help you avoid this crash.
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.