Strange behavior of android's ViewModel - android

When I try to simulate configuration change in my app by enabling "Don't keep activities" in developer options every time I leave an activity and return, the ViewModel is recreated! Aren't ViewModels supposed to handle these situations?
I can handle this problem by saving my activity's state in onSaveInstanceState but then what's the point of using a ViewModel?

When I try to simulate configuration change in my app by enabling "Don't keep activities" in developer options every time I leave an activity and return, the ViewModel is recreated!
AFAIK, "don't keep activities" destroys activities when you navigate away from them. It does not simulate configuration changes.
On Android 8.1, the setting specifically states: "Destroy every activity as soon as the user leaves it".
Aren't ViewModels supposed to handle these situations?
The ViewModel system handles configuration changes. It does not handle activities being destroyed or processes being terminated.
To simulate a configuration change, change the configuration. For example, you could rotate the screen or change your locale.
I can handle this problem by saving my activity's state in onSaveInstanceState
Anything that can go into the saved instance state Bundle should go into the saved instance state Bundle, as that handles both configuration changes and process termination.
what's the point of using a ViewModel?
ViewModel is there for things that cannot go into the saved instance state Bundle, such as:
Big things (Bitmap of a photo)
Live things (LiveData, RxJava Observable, etc.)
Wrongly-typed things (you cannot put a Socket in a Bundle)
Things that are not really part of the "instance state" and should not be needed in case Android terminates the process, but you would like to have them around for a simple configuration change
And so on

Related

Use-case of Android Lifecycle functions onRestoreInstanceState, onSaveInstanceState

I recently wrote a demo app, which just needed to display some data temporarily --- I meant for the data to disappear once the app was properly destroyed by the user. Toward this, I read the page
The Activity Lifecycle , which seems to recommend overriding the Activity methods
onRestoreInstanceState() and onSaveInstanceState().
It worked great! The data was preserved through screen rotations, and sending the app to the background.
But then I would leave the app running and walk away, and when I looked at it again, the data was gone.
I spent hours trying to de-bug my app, and re-reading that page.
Finally, I read
Saving UI States. It refers to overriding these methods as "ViewModel" approach, and explicitly states that data saved this way does not survive system-initiated process death --- which explains my observation.
My main question is: what on earth is the practical application of this "ViewModel" persistence approach? What is the use-case for a persistence mechanism that randomly disposes of data when the user isn't looking?
(I guess this is an old API left over from the times when apps didn't run in the background. But I don't see that reflected in the documentation.)
A second question is, reading the first page, how on earth was I supposed to understand this unfortunate behavior? Did I miss something? (It is very long.)
what on earth is the practical application of this "ViewModel" persistence approach?
It is not a persistence approach. A ViewModel is a way of holding onto state across configuration changes. Using a SavedStateHandle with ViewModel — which maps to onSaveInstanceState() and onRestoreInstanceState() — is also useful for a fairly narrow use case:
User is in your app and does something that you don't want to save to disk or the server (e.g., the user didn't click "Save" yet)
User turns off their phone screen or switches to another app (e.g., via system HOME navigation or the overview screen)
Time passes
Android terminates your app process to free up system RAM for other apps
Within ~30 minutes of having left your app, the user returns to your app
At this point, Android wants to pretend that your app had been around all along, despite the fact that your process had been terminated. So, Android will not only start up a fresh process for you, but it will recreate the last activity the user had been on... and you get your saved instance state back as part of this.
However, this is not a persistence approach. For data you want to have survive long term, you need to save it to disk (SQLite, SharedPreferences, JSON file, etc.) or to some server. Notably, if the user leaves your app for an extended period (over ~30 minutes), Android will not attempt to restore the instance state, and your app will be started normally.
You need to use a SavedStateHandle with a ViewModel to get data persistence when the system terminates your app in the background. Otherwise it's more about sharing data between components, and surviving Activity destruction e.g. on screen rotation without having to do a lot of boilerplate handling.
Just like with onSaveInstanceState, this is purely about persisting data when the system kills your running app to recover memory, so that when the user switches to the "running" app again, it can be recreated and restored exactly as it was. It doesn't save any data when the app is intentionally stopped, e.g. calling finish(), the user backing out or swiping it away etc.
This stuff should always just work - if you were seeing your data "go missing" and the app wasn't crashing in the background, it's possible your save/restore logic wasn't working. A good way to test that is going to Developer Options on your device (if you don't know how to get that do a search, it depends on your device) and enable Don't keep Activities. That will destroy them as soon as they go to the background and it should help you test how that's handled. The fact you were handling rotations ok suggests it was a background crash though, but that depends on how you were handling configuration changes

Architecture components ViewModel vs. savedInstanceState bundle

Trying to understand what is the difference with using the ViewModel to keep some of the state of the activity or fragment, and saving them with the savedInstanceState bundle.
Got a impression that the ViewModel instance is kept alive when the activity/fragment is destroyed by os in the case like configuration change so that when os recreate the activity/fragment could get the data from the ViewModel instance which is still valid.
Does it apply to minimize the app and re-open it?
Did some test, seems minimize the app and re-open the app, the os will recreate the activity/fragment with the stavedInstanceState bundle in the onCreate() not null (whatever is saved when the onSaveInstanceStae() is called). But the ViewModel has been cleared so a new instance is created without previous ones data.
Does it it mean although is in this case the os can retrieve the saved instance state and pass to activity/fragment's onCreate(), but the ViewModel has to be a new instance without previous instance's data, or the viewModel needs do to some extra step inorder to store/restore the data cross the instances?
If someone is still looking to understand difference between onSavedState vs ViewModel here is the detailed explanation:
onSavedInstanceState : Primary usage of onSavedInstance was not to handle orientation change but to provide a mechanism to retrieve data if app/activity is destroyed by Android System. Example case when app is in background and Android system decide to kill this as it needs memory for some other high priority process then in this case before activity is destroyed onSavedInstanceState will be called.
onSavedInstanceState only stores the Parcelable data, that provides hint to restore the state for the user when activity restarts. It saves data in System server that is a separate process.
onSavedInstanceState has data limit. Only small amount of Parcelable data can be saved.
While for ViewModel
ViewModel object is part of Applications process memory and hence it is able to survive configuration changes. Once a process dies, ViewModel goes away and all the saved state will be lost. Hence when activity restarts, ViewModel has nothing in it.
It works as a cache for heavy objects.
There are no restrictions in ViewModel.
Important: Always remember ViewModel and SavedState work together. They are not replacement or alternative to each other.
A good explanation (and a solution to your problem) can be found in this blogpost.
TLDR: the viewmodel is hosted inside a persisted fragment, which gets recreated together with the hosting activity.
From: Kristin Marsicano's Book “Android Programming: The Big Nerd Ranch Guide, 4th Edition.” :
ViewModel vs Saved Instance State
While saved instance state stores an activity record across process death, it also stores an activity record across a configuration change. When you first launch the activity, the saved instance state bundle is null. When you rotate the device, the OS calls onSaveInstanceState(Bundle) on your activity. The OS then passes the data you stashed in the bundle to onCreate(Bundle?).
ViewModel really shines when you use it to orchestrate dynamic data for the activity
ViewModel makes continuing a download operation across a configuration change simple. It also offers an easy way to keep data that was expensive to load in memory across a configuration change. And, as you have seen, ViewModel gets cleaned up automatically once the user finishes the activity.
ViewModel does not shine in the process death scenario since it gets wiped away from memory along with the process and everything in it. This is where saved instance state takes center stage. But saved instance state has its own limitations. Since saved instance state is serialized to disk, you should avoid stashing any large or complex objects.
lifecycle-viewmodel-savedstate is a new library that was just released to allow ViewModels to save their state across process death. This should alleviate some of the difficulties of using ViewModels alongside saved instance state from your activities.
Use saved instance state to store the minimal amount of information necessary to re-create the UI state (for example, the current question index). Use ViewModel to cache the rich set of data needed to populate the UI in memory across configuration changes for quick and easy access.
When the activity is re-created after process death, use the saved instance state information to set up the ViewModel as if the ViewModel and activity were never destroyed.
As of this writing, there is no easy way to determine whether an activity is being re-created after process death versus a configuration change. Why does this matter? A ViewModel stays in memory during a configuration change. So if you use the saved instance state data to update the ViewModel after the configuration change, you are making your app do unnecessary work. If the work causes the user to wait or uses their resources (like battery) unnecessarily, this redundant work is problematic.
One way to fix this problem is to make your ViewModel a little smarter. When setting a ViewModel value might result in more work, first check whether the data is fresh before doing the work to pull in and update the rest of the data.

Stopping the activity from recreating when resumed

I got two activities. When I jump from first to second activity and then come back to first one, the activity gets recreated in some phones. Can anyone tell me how to prevent it?
I don't believe you can prevent it. If you have some state to save, consider saving it. If you need things to keep running in the background, consider using a Service.
You can't stop the recreating from an App, but you can save your state in onSaveInstanceState(Bundle) and retrieve the state in onCreate(Bundle) or onRestoreInstanceState(Bundle).
It's the intended behaviour, and you shouldn't be trying to stop it. But, as mentioned by gnobal, you will need to save the state, and recreate your data to handle these situations properly.
Some phones with low memory, will always do that. A good way to test your implementation is to go to "Developer options" and enable "Don't keep activities".

Does Fragments with setRetainInstance(true) survive process shutdowns?

Considering this scenario: If I created an activity and it moves to the background and this activity contains a Fragment which is set to setRetainInstance(true) then the Android OS might at some point still decide to shut down the activity's hosting process in order to free memory.
Then the Activity's state is saved via onSaveInstanceState(Bundle) where - as far as I understood - the related Bundle is written and to the file system to survive the process shut down. (thus the requirement of objects in the bundle being Serializable). Later, the applications state can be retrieved in a new process via onRestoreInstanceState(Bundle).
In contrast, my Fragment is allowed to contain variables which are not necessarily Serializable. Therefore, I figured, the Fragment cannot be stored on disk like the Bundle is. So what happens to my fragment when the process gets killed?
I was wondering about this reading the developer's guide (http://developer.android.com/guide/components/processes-and-threads.html):
A process holding an activity that's not currently visible to the user
(the activity's onStop() method has been called). These processes have
no direct impact on the user experience, and the system can kill them
at any time to reclaim memory for a foreground, visible, or service
process. Usually there are many background processes running, so they
are kept in an LRU (least recently used) list to ensure that the
process with the activity that was most recently seen by the user is
the last to be killed. If an activity implements its lifecycle methods
correctly, and saves its current state, killing its process will not
have a visible effect on the user experience, because when the user
navigates back to the activity, the activity restores all of its
visible state.
I understood the above killing such that the VM instance is shut down and the state of the process is written to the file system (here comes the Bundle into play). Later the bundles are read for resuming the process. Since the retaining of fragments is not concerned with life cycle methods and since I would not know how to retain e.g. a pointer to a network connection (you should of course never have such a pointer in a fragment anyhow), I was wondering if the fragments are still restored if the process is shut down in the meantime. I concluded that they surely needed to be recreated and that the life cycle methods are therefore to be preferred over setRetainInstance(true) whenever possible.
Does this assumption make any sense?
Sounds like you're mixing up two concepts here.
Saving state across Configuration Changes does not involve serialization. If you request setRetainInstance() for a Fragment then that means it will fully stay in memory and not be re-created only for configuration changes. A similar mechanism is available for Activity objects but they need to explicitly define an Object which is going to be saved. This works via Activity.onRetainNonConfigurationInstance(), not via onSaveInstanceStae().
The other mechanism involves serialization and possibly (maybe not always, not sure) file system I/O to be able to reproduce state information even if an Activity/Fragment is destroyed (which happens independently of its hosting Process, btw). This works via Activity.onSaveInstanceState() and Fragment.onSaveInstanceState().
Of course, you can use the second mechanism for the purpose of the first, thus slowing down the way your app deals with configuration changes. Depending on your internal state, the slowdown could me marginal of significant.
Regarding your questions.
"My Fragment in contrast, is allowed to contain variables which are not serializable." Well, the same is true for your Activity. It can contain non-serializable objects which can be saved across config changes as described above.
"the fragment cannot be stored to disk when a process is shut down and must be recreated when an activity was restored." No, both mechanisms are available for both object types.
Hope I could contribute to clarifying this a bit.
Edit after your first comment.
Regarding the comment:
"onRetainNonConfigurationInstance is deprecated": Yes. I mentioned it for demonstration purposes because of a specific wording in your question. Also, with Android 2 devices having a 46% market share as per today (official Google figures), this method will definitely stay around for a very long time, deprecated or not.
"My main concern is about what will happen to the fragment instance when my hosting process is killed and removed from the memory": Your fragment instance will be removed from memory and there's of course no way it is restored as-is with its complete internal state automatically. This is only done when you setRetainInstanceState in the case of config changes. (But note that this relates to the Instance, in other words, the full object.)
Regarding your edit:
Once more, yes, your Fragment's Bundle will be stored and restored to/from the Bundle regardless of setRetainInstanceState if you use Fragment.onSaveInstanceState() for this purpose, for everything that makes sense.
It is not true that "all of its visible state" will be saved as the text you refer to claims; for example, the visibility attribute will not be saved. Whether that's supposed to be a bug or a feature I don't know, but it's a fact. But this is only a side remark; UI elements will save most of their relevant state.
"the state of the process is written to the file system": No! The state of objects which are able to save their state to a Bundle and actually implement saving their state will be saved in a Bundle, this means that you must provide such information yourself if you want your Fragment to save some state information. Also, again: No, this does not only relate to killing the process but also to deleting Activity and Fragment objects which are not visible; like the last Activity shown -- the Process may well stay alive.
"bundles are read for resuming the process": No, the Bundle will be read to pass it to the re-construction of Activity and/or Fragment objects, there is nothing done automatically in this process (except library objects which save their state also restore their state), but Android does not "resume" the "Process" from these Bundles.
"Since the retaining of fragments is not concerned with life cycle methods": Again, I think you're mixing up the two concepts. The "retaining" of a Fragment is only performed upon configuration changes _IF_ you request it via setRetainInstance, but we're mostly talking about the re-creation of Fragment objects from a Bundle here, which does involve the life cycle methods as documented by Google.
"I would not know how to retain e.g. a pointer to a network connection": Again, this must be a statement based on your mix-up. Of course you can keep a reference to a network connection upon config change (as requested per setRetainInstance) because when that happens, everything is simply kept in memory. Also, even if your Fragment gets deleted (because it became invisible) and your process is still there (because it shows the next Activity), you can (and should) keep references to objects which are expensive to re-create, such as a network connection, in your Application object, which exists as long as your process lives (more or less). It is only when your whole app is killed by Android that you lose everything, but the serialization we're discussing happens much more often.
Your conclusion:
I concluded that they surely needed to be recreated and that the life cycle methods are therefore to be preferred over setRetainInstance(true) whenever possible. Does this assumption make any sense?
Unfortunately not, since you are mixing up completely independent concepts.
I'll give it a final try:
You will want to keep a network connection reference which you need throughout your app in your Application object because it would be a lousy user experience if you created it from scratch on a regular basis throughout your app.
Your Application object will only die if Android kills your app.
Your Activity and Fragment objects will be deleted from your app regularly when the user moves forward within your app.
When the user presses "back", Android will re-create Activity and Fragment objects from Bundles using lifecycle methods. Saving something in a Bundle makes sense if you have expensive computations to do to re-create the internal state. You can live without the Bundle mechanism because Android will always save the Intent so if you don't do anything then you'll start without saved state.
When a configuration change occurs, Android lets you optimize user experience by keeping objects in memory across the config change. Here, Activity life cycle methods get involvwed and it's up to your implementation to use the saved data effectively. For Fragments, this is where setRetainInstance' comes into play: YourFragment` will survive the config change in memory if you set it.

Is it important to use savedInstanceState and onSaveInstanceState?

I have an application which only supports portrait mode. I'm passing all my arguments using serialization, passing by means of intents - intent.putExtra() ant then in onCreate() - getIntent().getExtras().getX(MY_PARAM_NAME)...
This works even when the system shuts down the VM, because of crashes related to other things. The activities seem to be started again with the correct parameters thanks to serialization.
So the question is, is save instance state necessary in my case? It seems to work well without it... didn't get any problems yet. But maybe I'm missing something, or didn't test enough.
As you've pointed out, if your Activity gets killed off (i.e. due to low resources), when it is recreated, it is passed the original Intent that started it. In your case, that means you get your serialized objects back.
Overriding onSaveInstanceState is important for the scenario where something has changed during the execution of your Activity (that hasn't been persisted elsewhere) that you would like to maintain in case it gets killed off.
For example, storing member variables in your Activity is dangerous for when the Activity is killed and recreated, unless you store them in the Bundle in onSaveInstanceState, and then restore them from the Bundle passed to onCreate.
Update: A great way to test the need for implementing that method is to force Android to kill your activities as soon as you leave them. Then, run your app and see if there are any problems. You can do this with the Dev Tools App on an emulator, or in ICS by going to Settings -> Developer options, and checking "Don't keep activities".
onSaveInstanceState() and onRestoreInstanceState() are only explicitly called by Android when the Activity needs to be recreated, generally after a configuration change (ex. changing orientation).

Categories

Resources