I was left with some questions regarding ViewModels after reading this:
https://developer.android.com/topic/libraries/architecture/saving-states
It says here that you should use a combination of both a ViewModel for configuration changes (like screen rotation), and using onSaveInstanceState() for all other cases where an activity is destroyed and then recreated in order to save the UI state.
My question is how do we know the way to restore the state when onCreate(Bundle) is called - should I use the ViewModel or should I use the bundle received as a parameter? When the configuration changes, onSaveInstanceState() is also called, and obviously onCreate() is always called.
If I only restore the state from a ViewModel, it won't always remain with the correct data (since the activity could have been destroyed due to other reasons than configuration changes). If I only use the bundle I save in onSaveInstanceState() then why would I use a ViewModel to begin with?
I think it's good to think of this sources as a chain.
You have 2 sources of data - ViewModel, that is faster but lives less and saved instance state that is slower but lives longer.
The rule is simple - try using your ViewModel and if it is not populated use the bundle from onSaveInstanceState().
When you do val model = ViewModelProviders.of(this).get(MyViewModel::class.java) in onCreate() you can check if you get a new instance of viewModel. Then, if it is a new instance (i.e. it's data fields are empty) you can get some basic data from your bundle, like content id, and fetch data from the backend or database based on that id, populate your new ViewModel with it and then populate your activity from the ViewModel (if you are using LiveData it will be very natural).
Next time onCreate is called you repeat the process, either populating your activity from ViewModel or populating your ViewModel using data in the Bundle and then populating your activity from your ViewModel.
Update:
Actually there is very similar approach described in the official docs. The only difference is that you pass the bundle to ViewModel and it decides if it needs fetching data, I was not specific about this mechanism.
Related
I have one Activity and want to share data between that Activity and fragments. I put data in extra while in a fragment and put also other data in it. That way I Have a shared Bundle across my application. I only see examples of passing a Bundle to an Intent but its also possible to change that data while in another fragment. This does not break with the self-containment of fragments. I dont put them in some method in activity because then you will have to cast the activity. Can anybody tell me its right to do? I know about shared pref but I dont want a file based solution. I know about passing parameters with newInStance but I also need to save data back in fragments. passing parameters is only forward not shared.
Passing data from activity/fragment back & forth using Bundles would have some limitations and issues for instance:
To pass a complex object, you'd need to use a serializable marked with a key.
Keeping shared keys in different parts can lead to runtime
errors or data loss if keys are wrong.
Serialized objects are not recommended, but it can be solved with Parcelables. But maintaining that is not that easy for complex objects check here, and you would need to customize that for different types of objects.
Still you don't share data among different fragments but they're just transported; and need to be transported over and over again when you go to a new part of your app.
Not guaranteed to keep the data if the activity is recreated.
Instead of that you'd use ViewModels through MVVM structure where:
Data can be shared on different levels of lifecycle scopes; this means that if the ViewModel is instantiated through ViewModelProvider in activity; then it can be accessed in any part of the activity, or underlying fragments. And if you want to keep only data shared between any fragment and its underlying fragments; you'd bound the ViewModel instantiation to that fragment instead.
ViewModel is instantiated once in the owner, and accessed in the subordinate fragments with no re-instantiation.
If the activity is re-created, it receives the same ViewModel instance that was created by the owner, and the view's data won't be lost.
When the owner activity/fragment is finished, the framework automatically calls the ViewModel's onCleared() method so that it can clean up the resources.
Here is a code lab that you'd check.
How does the Android Viewmodel works internally?
How Viewmodel save the data when the activity gets destroyed and recreated when the device get rotated
View Model's Internal Wokring:
View Model:
View Model is a lifecycle awared class, designed to store and manage UI related data. It a main component in MVVM architecture.
When a view model is created, it is stored inside activity or fragment manager.
Benefits:
Lifecycle awared
Hold and share UI data
Survives in rotation and retains data
Here we can raise a question that how we can get same instance of view model when new instance of activity is created while rotating screen from portrait to landscape ?
Answer:
To create a viewmodel object, ViewModelProvider class is required.
ViewModelProvider is the utility class which is used to create the instance of viewmodel in the following way.
Create a ViewModelProvider instance
Get the desired viewmodel from the viewmodel provider object
Internally creation of ViewModelProvider required two parameters.
ViewModelStoreOwner: it is an interface.It has just one method which returns the
ViewModelStore.
Factory: It is a nested interface in the ViewModelProvider class and is used to manufacture viewmodel objects.
val viewModelProvider = ViewModelProvider(this)
val viewModelProvider2 = ViewModelProvider(this,viewModelFactory)
If the factory is not passed then the default factory is created. Custom factory can be created for the parameterized viewmodel.
So now we have instance of viewmodel provider,
Now let’s get our viewmodel object
val viewModelProvider = ViewModelProvider(this)
val viewModel = viewModelProvider.get(LoginViewModel::class.java)
As we can see, we simply just called the get(arg) method with the desired viewmodel class reference and our viewmodel object was created.
So all the magic happens in this get method
This method gets the canonical name of the viewmodel class,creates a key by appending DEFAULT_KEY then it calls the another get function which takes the key and viewmodel class reference
This method checks the viewmodel instance in the viewmodel store first.If the viewmodel instance is there in the viewmodelstore then it simply returns that instance .If viewmodel instance is not there then it uses the factory to create the new instance and saves that instance in viewmodel store and then it return the viewmodel instance.
This viewmodel store is linked to ViewModelStoreOwner so our activity/fragment has their own viewmodel store.
It is ViewModelStore which stores the viewmodel and is retained when the rotation occurs and which returns the same viewmodel instance in the new activity instance.
Interview Question : Viewmodel store is linked to activity / fragment and when in the process of rotation current instance is destroyed and new instance is created then how this ViewModelStore object is still the same?
Let’s know about this magic
ViewModelStoreOwner is an interface. ComponentActivity implements this interface.
In above implementation , we can see that in the new activity object
when viewmodel store is null then it first checks with the
NonConfigurationInstance which returns the previous activity’s
viewmodel store.
If activity is being created for the first time then always new
viewmodel store objects will be created.
So It is NonConfigurationInstance object which is passed from old destroyed activity to newly created activity when rotations happens.It contains all the non-configuration related information including viewmodel store which contains the viewmodel of old activity object.
Answer is inspired by This Link
How does the Android Viewmodel works internally?
Android's ViewModel is designed to store and manage UI-related data in such a way that it can survive configuration changes such as screen rotations.
ViewModel gets called by an activity that previously called it, it re-uses the instance of that ViewModel object. However, if the Activity gets destroyed or finished, counterpart ViewModel calls the onClear() method for clearing up resources. Meaning if you have added something like this to your ViewModel:
override fun onClear() {
super.onClear()
clearAllLiveDataValues()
disposeAllVariables()
}
Function calls added here will be invoked.
How Viewmodel save the data when the activity gets destroyed and recreated when the device get rotated
ViewModel has its own lifecycle that allows itself to recover its state, and the transient data it holds, during screen rotations.
NOTE: Activity and ViewModel's lifecycle are both ephemeral. Allowing the ViewModel to handle critical or sensitive data during configuration changes IS NOT RECOMMENDED.
Your application should use either shared prefs, secured storage (if necessary), local database or cloud storage when you are expected to handle critical or sensistive data in a specific screen or part of your app.
I recommend that you read the following:
https://developer.android.com/topic/libraries/architecture/viewmodel
https://android.jlelse.eu/android-architecture-components-a563027632ce
https://medium.com/androiddevelopers/viewmodels-persistence-onsaveinstancestate-restoring-ui-state-and-loaders-fc7cc4a6c090
I do know how to save data between config changes (using onSavedInstanceState and checking the Bundle in onCreate, for example, for not being null) and I also do know how to make data persistent (via SharedPreferences), but here I need an in-between-solution.
I have a resource-heavy fragment which gets killed (onDestroyView() and onDestroy() are being called) when I replace it with another resource-heavy fragment. When I return to the former, its state is gone (the Bundle is null), making it persistent would save state across "sessions" which contradicts state-saving in other fragments. Is there a way how have "session-scoped" persistence in Android? By "session-scoped" I mean a period longer than the Android life-cycle and shorter than "forever".
So, there are a bunch of ways of saving the fragment and activity states.
If you have several fragments and an activity, and you are simply replacing the fragment view with those fragments then you may use graph level ViewModel. Check out navigation architecture component.
If you just want to save the state of the fragment, just create ViewModel for fragment view and make its lifecycle as activity lifecycle. So the ViewModel does not get cleared or killed until the host activity for the fragment is destroyed.
You may check out view model here in Android Documentation: ViewModel
Here is the good comparison for managing saved states, which options to consider 1) ViewModel, 2)Saved states or 3) Persistence storage
Depending on the size of data and context you may choose one of the given options. I would fully recommend reading it. Here is the link
Saving UI States
I read this, and it says:
For simple data, the activity can use the onSaveInstanceState() method
and restore its data from the bundle in onCreate(), but this approach
is only suitable for small amounts of data that can be serialized then
deserialized, not for potentially large amounts of data like a list of
users or bitmaps.
My question is why? How does the ViewModel differs from Bundle with regards to making the data persistent between instances?
Data stored in Bundle is serializable and could survive process die - you can restore it after application is launched again. On the other side, ViewModel only survive during configuration change (for example, rotation of the screen) and didn't save it's data if component die.
Bundle is class used to pass data between components of android like activities, fragments etc.
Mainly for activity, when activity is under any configuration changes, bundle helps to save some small amount of user data to be restored on new configuration applied. Due to limited memory provided for any App, it's stated that,
this approach is only suitable for small amounts of data that can be serialized then deserialized, not for potentially large amounts of
data like a list of users or bitmaps.
Also, it is not anything related to activity lifecycle.
From ViewModel guide, it's stated that
The ViewModel class is designed to store and manage UI-related
data in a lifecycle conscious way. The ViewModel class allows
data to survive configuration changes such as screen rotations.
So, any data which you need to survive any configuration changes can be part of ViewModel class, it can be any large amount of data.
Let me know if still confused, hope it helps !
Edit :
Thin line difference is
Bundle is something which is provided by activity once it's under configuration change. So, the OS parcels the underlying Bundle of the intent. Then, the OS creates the new activity (yes, it's new object of same activity), un-parcels the data, and passes the intent to the new activity.
ViewModel objects are something that are provided by ViewModelFactory which is out of context for activity & it doesn't rely on activity instance.
Android Architecture Components provide the LiveData and ViewModel classes which are more lifecycle-friendly and designed for a leaner Activity/Fragment. These classes handle storing data across configuration changes, but I'm confused about their use compared to the Activity framework APIs. Are onSaveInstanceState(Bundle) and onRestoreInstanceState(Bundle) still necessary or useful for preserving activity state?
onSaveInstanceState & onRestoreInstanceState is still useful.
ViewModel holds data only when process is alive.
But, onSaveInstanceState & onRestoreInstanceState can hold data even if process is killed.
ViewModel is easy to use and useful for preserving large data when screen orientation changes.
onSaveInstanceState & onRestoreInstanceState can preserve data when process is in background.(in background, app process can be killed by system at anytime.)
Assume a scenario :
user is in activity A , then navigates to activity B
but because of low memory Android OS destroys activity A , therefor the ViewModel connected to it also destroys. (You can emulate it by checking Don't keep activities in Developer options)
now user navigates back to activity A, Android OS try's to create new Acivity and ViewModel objects. therefor you loosed data in ViewModel.
But still values in savedInstanceState are there.
As well as the other answers which talk about the ViewModel's persistence beyond simply configuration changes, I think there are a couple more use cases:
Performance reasons
Sometimes you don't want to store all of the latest values of view attributes in the ViewModel for performance reasons. You may have greater need to save them when the view is being re-created. For example, user's scroll position on a view within your activity/fragment. You probably don't want to save the scroll position every time the user scrolls. But you might want to save that onSaveInstanceState so you can restore that when the view is recreated (onRestoreInstanceState).
Initialization to perform after restore
Some views may require initialization especially because of the restore, due to the complex design of those views not being able to save everything. For example, I had a WebView and if the user was in the middle of loading a page during a configuration change, I want the WebView to try to load the new page (rather than the old one). After restoring the state, the observers of LiveData will get the latest values but this doesn't help much with something like this (I only want the view to load a page from the ViewModel at the point of restore, not at other times). So we just do that initialization via the restore state.
Final word
With all this stuff I would advocate keeping your onSaveInstanceState and onRestoreInstanceState as simple as possible. Ideally just call a method on the ViewModel and that's it. Then we can extract all of the logic from the view into the ViewModel, and the view is just left with boilerplate code.