I've creating a test activity that updates some text in my MyViewModel.
I'd like to observe these changes in a Fragment, but when I use
MyViewModel myViewModel = new ViewModelProvider(this).get(MyViewModel.class);
it gives me a different instance of MyViewModel than that used in the activity, which results in my onChanged() callback in the fragment not being called.
Only when I modify that same fragment code to
HomeViewModel homeViewModel = new ViewModelProvider(getActivity()).get(HomeViewModel.class);
does the fragment get the same instance of MyViewModel as the activity - so onChanged() is successfully called.
However, I'm not sure if using getActivity() as the ViewModelStoreOwner is the proper way of doing things as I haven't seen this in any examples anywhere. I'm wondering if there might be a better ViewModelStoreOwner I should be using in this instance?
I'm wondering if there might be a better ViewModelStoreOwner I should
be using in this instance?
You should use activity instance for sharing the same instance among fragments in the same activity.
Both Activity and Fragment implements their own ViewModelStoreOwner interface and implements the getViewModelStore() method. getViewModelStore() provide the ViewModelStore instance which is used to store the viewmodel objects, created by the ViewModelProvider.
Note: ComponentActivity implements the ViewModelStoreOwner interface and FragmentActivity (parent of AppCompatActivity) inherits the implementation.
So both Activity and Fragment have specific implementation for the ViewModelStoreOwner interface methods and store the viewmodel instance as per the lifecycle of the objects(including the configuration changes).
Since fragments belong to activity get the same activity instance so using the getActivity() will result in using the ViewModelStoreOwner object of the activity. To share the objects among fragments, simply use the activity instance for creating ViewModelProvider which will use the same ViewModelStoreOwner in all fragments hence will return the persisted object of viewmodel (if created before).
Having an Activity which does as little as possible has become a "best practice" for some time now, so the scenario of an Activity and a Fragment which need access to the same ViewModel instance may not be covered by many guides.
But "there is no rule without an exception", and your scenario is similar to the one where an Activity has two Fragments which need to share data.
In this case, one uses the Activity scope for the ViewModel to make sure every component will have access to the same instance. See also the section "Share data between fragments" in the View Model Overview at developer.android.com
Related
Will Activity be lifeCycleowner of LiveData?
or
Will Fragment be lifeCycleowner of LiveData?
The answer is as per my understanding and I would say, it depends on where LiveData is being used or called from?
Let's go through some basics.
LifecycleOwner by definition means
A class that has an Android lifecycle. These events can be used by custom components to handle lifecycle changes without implementing any code inside the Activity or the Fragment.
Lifecycle
Lifecycle-aware components perform actions in response to a change in the lifecycle status of another component, such as activities and fragments. These components help you produce better-organized, and often lighter-weight code, that is easier to maintain.
Since the Lifecycle of Activity and Fragments are different.
Activity - Activity has its own lifecycle
Fragment - Each Fragment instance has its own lifecycle. To manage the lifecycle, Fragment implements LifecycleOwner.
Fragments are basically contained inside an Activity; so if the Activity is destroyed, Fragments will also be destroyed.
But it's not necessary that if fragments are destroyed, Activity will also be destroyed.
Live Data
You can register an observer paired with an object that implements the LifecycleOwner interface. This relationship allows the observer to be removed when the state of the corresponding Lifecycle object changes to DESTROYED.
Now coming back to your question.
when LiveData is observed in Activity, the lifecycle owner is Activity itself.
when LiveData is observed in Fragment, the lifecycle owner is Fragment itself.
What should be lifeCycleOwner of LiveData-Fragment or Activity?
It really depends on where you're using liveData. LiveData is lifecycle-aware, which means they respect the lifecycle of android app components.
If you are using liveData to observe data inside activity, then you should observe using activity.
If you are using liveData to observe data inside a fragment, then there is no point in using activity as lifecycleOwner. Using activity would lead to a memory leak, if fragment is replaced or removed, as liveData would still hold a strong reference to the fragment, which will be kept until activity is destroyed.
We should use viewLifecycleOwner inside fragments, as they are bind to the lifecycle of fragments.
As stated in the Google documentation
Each Fragment instance has its own lifecycle. When a user navigates and interacts with your app, your fragments transition through various states in their lifecycle as they are added, removed, and enter or exit the screen.
So each fragment and activity have their own lifecycle.
When you observer LiveData in the fragment or activity you pass the lifecycleowner associated with that fragment or activity.
I Have Fragment A that calls Fragment B or Fragment C.
B and C uses a ViewModel provided by A.
For while, I'm passing the View as parameter on constructor:
FragmentB(val viewModel: ViewModel)
FragmentC(val viewModel: viewModel)
But this way, not is a good solution, because the app is crashing when try to reopen the fragment.
Could not instantiate the fragment
What the best way to do this?
I thought create a newInstance method and pass the viewModel as argument on Bundle, but how I would transform this viewModel in Parcelable or Serializable?
You are probably trying to create custom Fragment constructor which is not allowed in Android.
I assume your ViewModel was created inside of fragment A using method
ViewModelProviders.of(this)
ViewModel created this way should be used only inside of this fragment(It won't crash if you i.e. pass it through Object or Singleton but it's not supposed to be used that way).
Better solution is to put all of these fragments A, B, C inside one activity.
This way you can call
ViewModelProviders.of(requireActivity())
and get the same instance of the ViewModel in all of the fragments. So if you get the ViewModel using this method in your fragment A, put there some values and then replace fragment A with fragment B in the same activity, you can call ViewModelProviders.of(requireActivity()) again get access to the same instance of your ViewModel and retrieve the values.
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 would like to ask if it's correct to share the same ViewModel between Fragment and its Activity.I have UserDetailActivity and UserDetailFragment. Can I use the same ViewModel to display detail data of a user in the UserDetailActivity and UserDetailFragment or is there better approach.
Yes you can pass ViewModal object from a Activity to Fragment or vice-versa by implementing Parcelable into ViewModal class and can share object with fragment setArguments() method.
I am not using MVVM but I think its the same with MVP, I use the same Presenter(ViewModel in your case) with my Activity and its child Fragment. It make since because a Fragment is literally a fragment of an Activity. There might be some special cases where you really want to separate viewModel of Fragment and Activity, but most of the time, they share. About the initialization, dont pass your viewmodel directly, you can use dagger and inject it.
AndroidViewModel is used to access Application context. I'm trying to access Activity's FragmentManager without passing it explicitly:
class FooViewModel(app: Application) : AndroidViewModel(app) {
private val fm = (app.applicationContext as Activity).fragmentManager
..
}
Getting error, unable to cast Context to Activity.
Question: is there any way to get FragmentManager inside AndroidViewModel without passing it explicitly?
I think short answer will be "no, there is no way", because Application context is in no aware of FragmentManager.
FragmentManager is an object that subclasses of FragmentActivity may have. Application is not a subclass of FragmentActivity.
Another question would be, why would you ever need a FragmentManager instance inside your ViewModel? Most possibly you should delegate view-related stuff to handle to other unit other than ViewModel (e.g. Activity, Fragment). Keep in mind, that this ViewModel would be retained over configuration change, thus if you keep a reference to the FragmentManager inside your ViewModel, you'd be leaking your Activity instance.