How to recreate ViewModel that was created with params injected via Factory after instance of activity is recreated?
ViewModel has a constructor like this:
class MyViewModel(
val model: MyModel,
val repository: MyRepository
) : ViewModel()
I instantiate it with factory:
ViewModelProviders
.of(this, MyViewModelFactory(
model = MyModel()
repository = MyRepository()))
.get(MyViewModel::class.java)
I tried to recover ViewModel like this, while savedInstanceState is not null (activity is recreated):
ViewModelProviders
.of(this)
.get(MyViewModel::class.java)
This causes a crash because no 0 arguments constructor is present inside MyViewModel class.
As, for passing factory each time:
My problem with that is, that whatever I pass as a MyModel to ViewModel, and that comes from activity Intent, might change later, due to user interaction. That would mean, when recreating, the MyModel in the Intent is outdated to the model that is already stored in ViewModel and was tampered by user interaction.
This causes a crash because no 0 arguments constructor is present
inside MyViewModel class.
It'll crash, as you're not passing any factory to construct the ViewModel.
How to recreate ViewModel that was created with params injected via
Factory after instance of activity is recreated?
AFAIK, you don't have to manually recreate the ViewModel on savedInstanceState. You may use viewModel to store data that are used in the activity.So, on recreation of the activity, the ViewModelProvider will not create a new instance of the viewModel but will give you the old instance and the data held in the viewModel will not be cleared.So there's no need to worry about savedInstanceState.
TIP: If you want to manage the creation of the factory and improve the recreation process. you may check this article on ViewModel with Dagger
There are no 0 argument constructors in MyViewModel. When you try to get the instance of ViewModel without providing a Factory it will look for a 0 argument constructor.
You can use this regardless of whether savedInstanceState is null or not.
ViewModelProviders
.of(this, MyViewModelFactory(
model,repository))
.get(MyViewModel::class.java)
Rather how you create repository and model changes on the basis of savedInstanceState value, depending on your use case or implementation.
Probably the only answer is, that it cannot be done this way. If the factory was provided, it has to be provided at all time. I don't know mechanism underneath ViewModel recreation but these are not as smart as I hoped.
My current implementation looks like this:
viewModel = ViewModelProviders
.of(this, MyFactory(MyRepository()))
.get(MyMViewModel::class.java)
val binding = DataBindingUtil.setContentView<ActivityCreateFoodstuffBinding>(this, R.layout.my_activity)
binding.viewModel = viewModel
if (savedInstanceState == null) {
val model = intent.getParcelableExtra<MyModel>("model")
viewModel.model.set(model)
}
I use one param constructor in ViewModel that always takes repository, but model I moved away and set it only when the activity is freshly created.
Related
My setup is: java Activity getting a ViewModel using Koin. All good with this, but when I rotate a phone, the ViewModel is always recreated. So how to avoid ViewModel recreation in the case? Thanks.
Activity:
private final FlowViewModel viewModel = get(FlowViewModel.class);
Koin:
val appModule = module {
...
viewModel { FlowViewModel(get()) }
}
You're injecting the viewmodel as a regular object with get() and not as a viewmodel. It does not get any viewmodel special treatment.
To use koin-managed viewmodels in java, you can add the library io.insert-koin:koin-android-compat and access viewmodels in koin graph with getViewModel() rather than get(). See https://insert-koin.io/docs/reference/koin-android/viewmodel#viewmodel-api---java-compat
After recently migrating from Dagger to Hilt I started observing very strange behavior with respect to ViewModels. Below is the code snippet:
#HiltAndroidApp
class AndroidApplication : Application() {}
#Singleton
class HomeViewModel #ViewModelInject constructor() :
ViewModel() {}
#AndroidEntryPoint
class HomeFragment : Fragment(R.layout.fragment_home) {
private val homeViewModel by viewModels<HomeViewModel>()
override fun onResume() {
super.onResume()
Timber.i("hashCode: ${homeViewModel.hashCode()}")
}
}
#AndroidEntryPoint
class SomeOtherFragment : Fragment(R.layout.fragment_home) {
private val homeViewModel by viewModels<HomeViewModel>()
override fun onResume() {
super.onResume()
Timber.i("hashCode: ${homeViewModel.hashCode()}")
}
}
The value of hashCode isn't consistent in all the fragments. I am unable to figure out what else am I missing for it to generate singleton instance of viewmodel within the activity.
I am using single activity design and have added all the required dependencies.
When you use by viewModels, you are creating a ViewModel scoped to that individual Fragment - this means each Fragment will have its own individual instance of that ViewModel class. If you want a single ViewModel instance scoped to the entire Activity, you'd want to use by activityViewModels
private val homeViewModel by activityViewModels<HomeViewModel>()
What Ian says is correct, by viewModels is the Fragment's extension function, and it will use the Fragment as the ViewModelStoreOwner.
If you need it to be scoped to the Activity, you can use by activityViewModels.
However, you typically don't want Activity-scoped ViewModels. They are effectively global in a single-Activity application.
To create an Activity-global non-stateful component, you can use the #ActivityRetainedScope in Hilt. These will be available to your ViewModels created in Activity or Fragment.
To create stateful retained components, you should rely on ~~#ViewModelInject, and #Assisted~~ #HiltViewModel and #Inject constructor to get a SavedStateHandle.
There is a high likelihood that at that point, instead of an Activity-scoped ViewModel, you really wanted a NavGraph-scoped ViewModel.
To get a SavedStateHandle into a NavGraph-scoped ViewModel inside a Fragment use val vm = androidx.hilt.navigation.fragment.hiltNavGraphViewModels(R.navigation.nav_graph_id).
If you are not using Hilt, then you can use = navGraphViewModels but you can get the SavedStateHandle using either the default ViewModelProvider.Factory, or the CreationExtras.
Here's an alternative solution to what ianhanniballake mentioned. It allows you to share a view model between fragments while not assigning it to the activity, therefore you avoid creating essentially a global view model in a single activity as EpicPandaForce stated. If you're using Navigation component, you can create a nested navigation graph of the fragments that you want to share a view model (follow this guide: Nested navigation graphs)
Within each fragment:
private val homeViewModel: HomeViewModel
by navGraphViewModels(R.id.nested_graph_id){defaultViewModelProviderFactory}
When you navigate out of the nested graph, the view model will be dropped. It will be recreated when you navigate back into the nested graph.
As mentioned by other posts here, using the by activityViewModels<yourClass>() will scope the VM to the entire Activity's lifecycle, making it effectively a global scope, to the entire app, if it's one activity architecture everyone uses and Google recommends.
Clean, minimal solution:
If you're using nav graph scoped viewmodels:
Replace this:
val vm: SomeViewModel by hiltNavGraphViewModels(R.id.nav_vm_id)
with below:
val vm by activityViewModels<SomeViewModel>()
This allows me to use this VM as a sharedviewmodel between the activity and those fragments.
Otherwise even the livedata observers do not work, as it creates new instances and lifecycles that are independent from each other.
Inside my fragment class, when I am getting my viewModel, I can write my code in two different ways.
Using "viewModelStore"
ViewModelProvider(viewModelStore, viewModelFactory).get(FragmentViewModel::class.java)
Using "this"
ViewModelProvider(this, viewModelFactory).get(FragmentViewModel::class.java)
My question is, does any difference exist between the two alternatives, and if yes which one is the preferable approach?
If you're providing your own ViewModelProvider.Factory, then there's no difference, so just use what is easier, this.
Of course, if you're in Kotlin, you don't need to use ViewModelProvider directly at all, you'd instead want to use Fragment KTX and use
val viewModel: FragmentViewModel by viewModels { viewModelFactory }
Note that if you were not using your own factory, you should always pass in a ViewModelStoreOwner (i.e., this) instead of just passing in the ViewModelStore since the Javadoc explicitly mentions:
This method will use the default factory if the owner implements HasDefaultViewModelProviderFactory. Otherwise, a ViewModelProvider.NewInstanceFactory will be used.
The ViewModelStore constructor is not able to get the correct default factory.
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
In Kotlin I'm using
viewModel = ViewModelProviders.of(this).get(HomeViewModel::class.java)
To retrieve a ViewModel from the providers.
Inside my ViewModel I have something like this.
val liveChuchuData = MutableLiveData<DataChuchu>()
From my understanding this creates a final new variable of MutableLiveData right?
I remember when declaring MutableLiveDatas in ViewModel in Java, we create a function and then check if the MutableLiveData is null to only create it once.
So what if I have a fragment that will also use the same ViewModel instance.
val liveChuchuData = MutableLiveData<DataChuchu>()
Will that line cause the current data to be reset, once called in a fragment?
Depends on what is the parent of your ViewModel. If parent is Acivity and in your Fragment you initialize your ViewModel with getActivity() instead of passing this, then you will reuse that ViewModel, but for example if you have two separate Fragments that initialize same ViewModel by passing this to ViewModelProvider then your ViewModel will have two separate instances and different data in them.
To have same data in ViewModel in two Fragments, you need to pass getActivity(); to ViewModelProvider when creating your ViewModel instance.
That said, YES, it will cause your data to be reset if you use this when creating ViewModel.
Hope this helps. Good luck :)