How to share a ViewModelProvider.Factory instance between Activities - android

I have a ViewModelProvider.Factory instance that is responsible for instantiating ViewModels and injecting repositories into the ViewModels.
This works well for Fragments as I simply supply my custom Fragment factory with the ViewModel factory:
this.supportFragmentManager.fragmentFactory = MyFragmentFactory(myViewModelFactory)
The fragment factory injects the ViewModel factory into the Fragments when instantiating them. The Fragments can then access the ViewModels through:
mapViewModel = ViewModelProvider(this, myViewModelFactory).get(MyViewModel::class.java)
However, I want to launch a new Activity that will also require access to the same ViewModel factory.
Can I share my ViewModelProvider.Factory instance across Activities without using a global variable?

Use the Common class concept. Make a common class and a public static variable of your required Datatype and store values there and when you need to access just use Common.YOUR_VARIABLE_NAME;
Public class Common{
public static REQUIRED_DATATYPE variableName;
}
store the value which you need to access in another activity as
Common.variableName = YOUR_VALUE;
and when you need to use just type Common.variableName
Feel free to ask if something is unclear.

Related

Want to know about the differences between the following topics:

I researched a lot but I didn't understand the basic difference between the following topics. Please, tell me what is the basic difference between them and when to use them. And if possible please provide article links other than the Android developers guide. Thanks a lot!
ViewModel
AndroidViewModel
ViewModelProvider.Factory
ViewModelProvider.NewInstanceFactory
ViewModelProvider.AndroidViewModelFactory
It's easier to start by explaining the factories.
ViewModelProvider.Factory is necessary because the framework needs a way to be able to create instances of your ViewModel on your behalf, because it has to do this when returning from process death. You don't create instances directly yourself except inside the Factory.
The framework automatically provides some default factories that are capable of creating instances of your ViewModel if your ViewModel constructor's arguments are one of the following:
constructor() Empty constructor (no arguments)
constructor(savedStateHandle: SavedStateHandle)
constructor(application: Application)
constructor(application: Application, savedStateHandle: SavedStateHandle)
So if your ViewModel constructor is like one of the above, you don't have to create your own ViewModelProvider.Factory. In your Fragment or Activity, you can simply use private val viewModel: MyViewModel by viewModels() to create it using the default factories.
You don't need to touch or even think about NewInstanceFactory or AndroidViewModelFactory. They are subtypes of Factory that the framework uses as the default implementations that can construct the above types of ViewModels.
AndroidViewModel is a ViewModel with an Application property. If your ViewModel constructor is like one of the last two in the list above and you want to avoid creating your own factory, you must subclass AndroidViewModel instead of ViewModel. The default factories for some reason will only handle those last two if your factory is an instance of AndroidViewModel. (Seems like an unnecessary and pointless restriction to me, but maybe I'm missing something.)

Hilt creating different instances of view model inside same activity

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.

When getting the ViewModel in a fragment, should I provide "viewModelStore" or "this" as the ViewModelStore to the ViewModelProvider() method

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.

Get ViewModel ViewModelProvider.Factory and Application Context

I am extending AndroidViewModel to get an application context in my ViewModel but Now I also want to pass some parameters to my ViewModel.
After some google search, I came to know that I can use ViewModelProvider.Factory to get Parametrized constructor of my MyViewModel but how to get an application context.
Thanks in advance.
You would need an Application in ViewModel.Factory to instantiate an AndroidViewModel.
Ways to achieve this
if you are instantiating the ViewModel in your Activity/Fragment then do getApplicationContext() and cast it as Application. If in fragment you can get the hosting Activity and get Application from it.
// Kotlin code
viewModel = ViewModelProviders.of(this,
ViewModel.Factory(activity?.application!!, param1, param2)) // from an fragment onViewCreated()
if your app has an Application class expose an method to get an application instance.
// JAVA code
public static Application getApp() {
return YourApplication.instance; // instance will be an static field in Application class
}
From your activity you can use:
Kotlin:
applicationContext
Java:
getApplication().getApplicationContext()

Share ViewModel between fragments that are in different Activity

I have a ViewModel named SharedViewModel:
public class SharedViewModel<T> extends ViewModel {
private final MutableLiveData<T> selected = new MutableLiveData<>();
public void select(T item) {
selected.setValue(item);
}
public LiveData<T> getSelected() {
return selected;
}
}
I've implemented it based on SharedViewModel example on the Google's Arch ViewModel reference page:
https://developer.android.com/topic/libraries/architecture/viewmodel.html#sharing_data_between_fragments
It is very common that two or more fragments in an activity need to communicate with each other. This is never trivial as both
fragments need to define some interface description and the owner
activity must bind the two together. Moreover, both fragments must
handle the case where the other fragment is not yet created or not
visible.
I have two fragments, called ListFragment and DetailFragment.
Until now I used these two fragments inside an activity called MasterActivity, and everything worked well.
I got the ViewModel in ListFragment, selected the value to use it on DetailFragment.
mStepSelectorViewModel = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
However, now, in certain cases, I need that ListFragment (a layout to a different device configuration) will be added to a different activity, called DetailActivity. Is there a way to do that similarly to the above example?
A little late but you can accomplish this using a shared ViewModelStore. Fragments and activities implement the ViewModelStoreOwner interface. In those cases fragments have a store per instance and activities save it in a static member (I guess so it can survive configuration changes).
Getting back to the shared ViewModelStore, let say for example that you want it to be your Application instance. You need your application to implement ViewModelStoreOwner.
class MyApp: Application(), ViewModelStoreOwner {
private val appViewModelStore: ViewModelStore by lazy {
ViewModelStore()
}
override fun getViewModelStore(): ViewModelStore {
return appViewModelStore
}
}
Then in the cases when you know that you need to share ViewModels between activity boundaries you do something like this.
val viewModel = ViewModelProvider(myApp, viewModelFactory).get(CustomViewModel::class.java)
So now it will use the Store defined in your app. That way you can share ViewModels.
Very important. Because in this example the ViewModels live in your application instance they won't be destroyed when the fragment/activity that uses them gets destroyed. So you will have to link them to the lifecycle of the last fragment/activity that will use them, or manually destroy them.
Well, I created a library for this purpose named Vita, You can share ViewModels between activities and even fragments with different host activity:
val myViewModel = vita.with(VitaOwner.Multiple(this)).getViewModel<MyViewModel>()
The created ViewModel in this way stay alive until its last LifeCycleOwner is destroyed.
Also you can create ViewModels with application scope:
val myViewModel = vita.with(VitaOwner.None).getViewModel<MyViewModel>()
And this type of ViewModel will be cleared when user closes app
Give it a try and kindly let me know your feedback:
https://github.com/FarshadTahmasbi/Vita
you can use factory to make viewmodel and this factor will return single object of view model.. As:
class ViewModelFactory() : ViewModelProvider.Factory {
override fun create(modelClass: Class): T {
if (modelClass.isAssignableFrom(UserProfileViewModel::class.java)) {
val key = "UserProfileViewModel"
if(hashMapViewModel.containsKey(key)){
return getViewModel(key) as T
} else {
addViewModel(key, UserProfileViewModel())
return getViewModel(key) as T
}
}
throw IllegalArgumentException("Unknown ViewModel class")
}
companion object {
val hashMapViewModel = HashMap<String, ViewModel>()
fun addViewModel(key: String, viewModel: ViewModel){
hashMapViewModel.put(key, viewModel)
}
fun getViewModel(key: String): ViewModel? {
return hashMapViewModel[key]
}
}
}
In Activity:
viewModelFactory = Injection.provideViewModelFactory(this)
// Initialize Product View Model
userViewModel = ViewModelProviders.of(this, viewModelFactory).get(
UserProfileViewModel::class.java)`
This will provide only single object of UserProfileViewModel which you can share between Activities.
I think we still get confused with the MVVM framework on Android.
For another activity, do not get confused because it must necessarily be the same, why?
This makes sense if it has the same logic (even if the logic could still be abstract in other useful classes), or if the view in the XML is almost identical.
Let's take a quick example:
I create a ViewModel called vmA, and an activity called A and I need the user's data, I will go to insert the repository in vmA of the User.
Now, I need another activity that needs to read user data,
I create another ViewModel called vmB and in it I will call the user repository.
As described, the repository is always the same.
Another way already suggested is to create N instances of the same ViewModel with the implementation of the Factory.
If you want a ViewModel that is shared by all your activities (as opposed to some),
then why not store what you want stored in that ViewModel
inside your Application class?
The trend presented at the last Google I/O seems to be to abandon the concept of Activities in favor of single-activity apps that have a lot of Fragments.
ViewModels are the way to remove the great number of interfaces the activity of an interface formerly had to implement.
Thus this aproach no longer makes for giant and unmaintainable activities.
Here's a link
Hope it helps you. O(∩_∩)O~
In addition:
1) The inspiration for the code came from smart pointer in c++.
2) It will be auto cleared when no activities or fragments references ShareViewModel.
The ShareViewModel # onShareCleared() function will be called at the same time!
You don't need to destroy them manually!
3) If you use dagger2 to inject the ViewModelFactory for share the viewmodel
between two activities (maybe three), Here's sample

Categories

Resources