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.
Related
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?
I'm using navigation graph to navigate between fragments and I noticed that my viewModel is cleared (onCleared) only when I press the back button, but not when I navigating to another fragment using this code:
val action = MyFragmentDirections.actionMyFragmentToParentFragment()
val navController = findNavController()
navController.navigate(action)
In the logs I see that the fragment onDestroyView() is called but the viewModel's onCleared() is not called.
What am I'm missing?
The framework keeps the ViewModel alive as long as the scope of the activity or fragment is alive.
A ViewModel is not destroyed if its owner destroyed for configuration change,such as screen orientation (in this case also the onDestroy() method called.)
The new instance of owner reconnects to the existing ViewModel instance. But if we intentionally want to finish the activity,then ViewModel will clear.
onClear() method is called before the cleaning occurs.
ViewModel is also cleared on onBackPress(). Because in this case, we also finish the activity intentionally.
Decision:
ViewModel is cleared when -
onBackPressed called
finish() method called
Activity is shut down by the system
due to memory issues.
ViewModel objects are scoped to a Lifecycle. They remain in memory until that Lifecycle object goes away permanently. In the case of an Activity, that's when it is finished, and for a Fragment, when it's detached. See ViewModel Overview for more information.
Without seeing more of your code, it's difficult to answer why onCleared is called on back press and not when the Fragment is destroyed. However, if your ViewModel is tied to the Activity then hitting back might finish the activity and therefore trigger the call to onCleared. Share more of your code if you want a better chance at someone helping you dig deeper.
I want to know if oncleared of viewmodel is called when onDetach of a fragment is called. This is to make sure that all coroutines will be cancelled. I was getting a IllegalStateException: Fragment not attached to a context before refactoring to kotlin and coroutines. Now I am using viemodelscope to do these tasks.
If any Context or UI related logic has to be executed on the result of any asynchronous or API calls, can result in this issue even if onDetach is called. You should make safe calls like null checking to get rid of that exception.
As Google notes on the official documentation:
Figure 1 illustrates the various lifecycle states of an activity as it
undergoes a rotation and then is finished. The illustration also shows
the lifetime of the ViewModel next to the associated activity
lifecycle. This particular diagram illustrates the states of an
activity. The same basic states apply to the lifecycle of a fragment.
And this is the image:
So the answer is: ViewModel's onCleared is called when onDestroy is called from Activity/Fragment .
As for your coroutines, you should cancel() the job on the onCleared()
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 am having an app which is retrieving data in the main activity and sending an event to all fragments as soon as it is available. So for the first start it looks like this:
App starts (fragments are initialising in the background) -> feed download -> notification sent to fragments -> fragments initialise UI
Everything's fine so far. BUT, what if I am resuming the app. The data will be still cached, so i will send the event immediately on app resume, and therefore it can happen that my fragments are not even ready for receiving the event -> no fragment UI update!
Or the event is triggered and received in the fragment, but the fragment is not ready for the UI update, cause it still hasn't inflated the layout -> NullpointerException
Or the fragment receives the event, but is not attached to the activity anymore -> another Exception.
There are ways to deal with single issues, but overall it is complicating the architecture a lot.
Somehow I tried a lot of things (playing around with Otto bus) but somehow I can't find any architecture which is working for making a central datasource available to all activities and fragments in the app.
How do you supply your fragments with data if you don't want to use bundles?
First of all a Fragment should be independent from other parts of an app. Moreover it shouldn't know parent activity: getActivity method should return just an Activity which could be casted to some interface.
an Activity shouldn't be a "data downloader". Basically activity is a View which receives various system and user events and displays particular state. For instance when the system creates activity it calls method 'onCreate' where activity should create/arrange fragments and views.
there is should be some manager or controller(call it as you wish) which knows where and how to get data for views. For instance if there is no internet connection it loads data from local database otherwise it makes network request.
So roughly speaking flow should look like this:
fragment(or activity) has reference to the DataManager. The fragment subscribes on FeedDataEvent in the onResume method. When fragment wants(onResume method for example) to show some data to the user it calls DataManager.loadFeed() and displays to the user "loading..."
DataManager checks if there is Task which is loading data from network. If there is no such fast it starts it.
When data is downloaded DataManager emits FeedDataEvent.
If the fragment is still visible it receives that event and shows data. If the user left the app fragment unsubscribed(in the onStop method) from FeedEventData and will not receive that event.
There is subtle thing with requests caching(making network request on every onResume is not very good idea) but it depends on particular app.
PS Almost all this things are implemented in RoboSpice and some other libraries.