I'm trying to clear out my view models after a flow through a couple of fragments has completed. When navigating from the last fragment in the flow to the start screen, I call viewModelStore.clear() which as far as I understand, should clear out all view models. However, if I go through the flow again, data from the previous flow is persisted.
actionButton.setOnClickListener {
viewModelStore.clear()
findNavController().navigate(R.id.action_global_startScreenFragment)
}
I tried to move the clear call into onDestroy and onDestroyView in my fragment as well, but nothing changes.
Am I misunderstanding something about clearing view models, or could someone maybe help me to understand the issue?
Related
I have a viewpager with 10+ pages in it, each page corresponds to its PageFragment and PageViewModel instances. When I start to swipe fragments one after another the onCleared() method is called for viewmodel that was left behind 2-3 steps ago, for example when I'm on page 7 the 4-th viewmodel is destroyed. The problem is, when I reach position 8-10 the onCleared() method starts to trigger also for this active viewmodel, which results to bad data representation on screen. Official documentation says that onCleared() is called whenever the viewmodel is not used anymore and should be destroyed, but how should it be destroyed if its data is represented on the active fragment
I tried to find any info about onCleared() method, but found almost nothing. Stack trace contains nothing suspicious. I presume that there is a force lifecycle methods call mechanism, but possible causes of its intervention in my case are unknown to me.
Please share your thoughts why is this behavior happening and how could I fix it.
I have done ample research on this, and there is not one clear solution on the problem.
In the life-cycle, particularly in the Fragment life-cycle, following Exception comes any moment after onPause().
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
My logic says, that to continue with the current fragment, after it reaches this state, I have to restart the activity and again point back to the intended fragment using Intent.
I want to be clear on what is happening and what should be real solution to deal with it.
I need to know the pros and cons of this mechanism; its importance in Fragment or Activity life-cycle.
Also, if I am changing the Windows Feature in onCreate to not to go to sleep, unless if the user has manually pressed the home button, will still the activity will go to this state?
This exception happens when you're trying to add/remove/replace/interact in any other way with a Fragment inside the Activity when it's paused.
Which means Activity will not be able to restore it's state (restore the state of a Fragment which has been changed) if it will be destroyed right away.
Best solution here, is to check that Activity is NOT paused during the interaction with a Fragment.
Another option is to use commitAllowingStateLoss() to interact with Fragment transaction, with a risk of losing it's state.
See:
https://developer.android.com/reference/android/app/FragmentTransaction.html#commitAllowingStateLoss()
In a perfect world you should analyze each crash carefully and add checks to verify that you interact with fragments only when Activity is up and running.
A better explanation is presented in a new Android developer reference and guide documents for using JetPack Life Cycle Listener.
https://developer.android.com/topic/libraries/architecture/lifecycle#kotlin
The library makes the components Activity Life Cycle aware. That means you do not require an abstract baseActivity class which overrides every life cycle callback, and record that state in a boolean variable. LifeCycle listener will do it for you.
All you have to do is stop introducing a new fragment or stop any Loader that updates the UI when its response returns. The right time to do this is before onStop or onSavedInstance state is called, and your components will be made aware of it.
It clearly states that after the onSavedInstancState or onStop is called the UI becomes immutable till the onStart of the Activity is called again. Sometimes you have to call restart the same activity using NEW TASK and CLEAR TASK flags using intent, when this state occurs and there is no chance that otherwise onStart is going to be called.
Happy Coding :-)
i have a fragment that is hosted inside of an activity. when user prsses the back button i need to save the model data and have it available the next time user opens the fragment/activity. But just while in the app, it does not need to be persisted to disk. So for example if user destroyed the process, then there is no need to keep the model data, it can be fetched from network again.
what i have tried:
icePick and onSavedInstance calls but these dont seem to kick in when user presses the back button on the fragment. tell me if im wrong.
here is what i have implemented in my fragment:
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable("myModel", Parcels.wrap(myModel));
}
i am using the parceler library if that makes any difference. I can also convert the code to kotlin if required. when i hit the back button the fragment gets popped off the stack and the activity contain it calls onDestroy but im not getting any call back in onSaveInstanceState. Also when i check in onCreate() savedInstanceState is null. I have not overrided onSavedInstance in the activity, just in the fragment. What am i doing wrong ?
i had a though to use a database to do this, but i just need it while in memory and there should be a way to do this without a DB.
from what i learned if user hits the back button onSaveInstance is not called by the system:
If an activity is in the foreground, and the user taps the Back button, the activity transitions through the onPause(), onStop(), and onDestroy() callbacks. In addition to being destroyed, the activity is also removed from the back stack.
It is important to note that, by default, the onSaveInstanceState()
callback does not fire in this case.
source: here
#onSaveInstanceState of fragment is strictly coupled to activity lifecycle
According to doc
Called to retrieve per-instance state from an activity before being
killed
You operates only with fragments and activity is left untouched,
so this method is definitely can't be used in your case and shouldn't.
My suggestion is to use some kind of persistent storage though interface. It could be in memory storage (any type of singleton, like suggested in comments. It may be scoped to app or activity or to custom case (you have to control manually cache lifecycle) and injected with dagger, for example), shared-preferences based storage, database storage. It is easy to test if you follow dependency injection patterns & use structural pattern like MVP (but it is not a point of this question)
So store the data in the repository on change or in the onPause method (because it is the last guaranteed to call when screen is being gone). And restore it in onCreate
I have an app where I use Butterknife, and recently I found a fragment where I had failed to call unbinder.unbind() in the fragment's onDestroyView(). I fixed the problem but it made me start thinking.
What kind of errors can this cause and why? I don't have a particular error right now but I would like to know what to watch out for in the future, and the website for the library doesn't specify the problems this could cause.
Imagine you have a retained fragment and you have initialized a view using #BindView.
An orientation change happens, which results in destroying activity instance, but not this fragment, because this fragment is a retained fragment, which means, that the field that you have initialized is still there (not null) and is holding a strong reference to the view of the previous activity, which results in leaking of the activity.
Although this might take for some small amount of time (because eventually you are going to perform another ButterKnife.bind() in onViewCreated(), right? But who knows, maybe you won't), still it is better to release resources as soon as you do not need them and let the GC do its job.
I've also thought about this question some time ago and other than this I couldn't come up to another scenario where unbind() would be strongly necessary.
I have inherited some code hence I don't have true freedom to change it. :(
I have a main activity, from which other activities (I will refer to these as sub activities from now on) are called. Whenever one of these completes, it calls finish and returns data to the main activity.
Each activity (including the main one) has a bar on the top that displays a custom view. The custom view contains a canvas which has a drawing that is dependant upon the state of the network.. i.e. wifi/mobile etc...
Since that 'state' data never changes, it's held within a singleton and the view gets data from the singleton to define what it draws. That is working with no issues, i.e. the data is always as I expect it.
When I first launch the MainActivity, as the network changes, the data changes and each call to 'invalidate' the view receives a system call to 'onDraw' as I would expect.
In each of the sub activities the same is again true.
Upon finishing a sub activity and returning to the mainActivity, calls to invalidate no longer cause a call to onDraw to occur.
I have looked at this for quite a while now and just cannot figure out what is going wrong.
In my constructor I have:
setWillNotDraw(false);
Whenever the data changes the following methods are called:
invalidate();
requestLayout();
Now, there's one more thing... upon returning to the activity at that immediate point, I refresh and this DOES draw correctly, i.e. invalidate does trigger an onDraw call... any subsequent network changes (which are propogated) fail to result in the onDraw call.
I'm wondering if this is to do with the view somehow being detached. I can see that 'onDetachedFromWindow' is called, however the trigger for this is the destruction of the subactivity, hence I don't see why that should affect the MainActivity but it's the only thing I can think of.
I'm hoping I've provided enough information for someone to help me...
Well, in the end my answer has very little to do with the question and I guess this is an example of how an issue can be solved by going back to absolute basics and checking for the obvious.
My activities all inherit from an abstract activity. Within that activity there is an instance of the view. The views in which I was having trouble were using that declaration as opposed to having their own instance, hence behaviour from one activity was then affecting another inadvertently.
So, if I'd been able to post up all the code, I'm sure someone else would have spotted this but, unfortunately I couldn't in this instance.
Still, whilst this posting doesn't provide a resolution that will help others, maybe it does say... step back and check the obvious first!