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 :)
Related
I have two Fragments and two ViewModels with a similar implementation. So, I decided to apply inheritance. So I decided to create a FragmentParent and a ViewModelParent, and then ended up with:
FragmentA and FragmentB both inherit from FragmentParent.
ViewModel(A) and ViewModelB both inherit from ViewModelParent.
On one side, both parents will be abstract because they have methods to be implemented in different ways. On the other side, both children ViewModels have the common parent's viewmodel methods and also their personal custom methods.
So, viewmodel object has to call some common methods from the FragmentParent but, also, the problem is that each Fragment will call their correspondent viewmodels' custom methods. So, if I declare the viewModel object in the FragmentParent, once I use it in the children to call the custom methos of each correspondent viewmodel, it says error because the viewModel object's type corresponds to the ViewModelParent.
As you can see in the image, the methods in colour cannot be called because vM is instance of ViewModelParent and they belong to the custom ViewModels.
A solution could be casting the viewmodel object in each child fragment once I need to call the custom methods, BUT, I guess this is dirty. Any good idea for this approach? Thanks.
Casting the parent ViewModel to the specific child ViewModel seems to be the best option. It's a common use of casting, I don't see any problem with it. If you're accessing methods of the child ViewModel multiple times inside the child Fragments, you can store the casted ViewModel in a property or variable in each child Fragment. For example, if you're using Kotlin you could do something like this:
//FragmentA
val viewModel = vM as ViewModelA
viewModel.customAmethod()
//FragmentB
val viewModel = vM as ViewModelB
viewModel.customBmethod()
For eg, As ViewModel should be loosely coupled we cant pass interface reference to ViewModel to get a callback from Viewmodel and call the method implemented in the fragment.
Also please provide an example or reference to call with methods with two or more params using livedata.
Make a live data variable in ViewModel and attach an observer in Fragment to that variable and whenever there will be a change in data then the function will be automatically invoked.
For Example:
viewModel.loading.observe(viewLifecycleOwner, { loading ->
loading?.let {
// Here, I'm calling a new function named setLoaderVisibility
setLoaderVisibility(loader = binding.shimmerProductDetail, binding.containerBody, isLoaderVisible = loading)
}
})
Feel free to ask if something is unclear.
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.
Sorry for the stupid question but upon reading about ViewModel i came across
randomViewModel = ViewModelProviders.of(this).get(RandomViewModel::class.java)
I just want to know what the of() is in general. Is it just a function used by the providers? Or is it a special operator?
Thanks
#Deprecated
#NonNull
#MainThread
public static ViewModelProvider of(#NonNull Fragment fragment) {
return new ViewModelProvider(fragment);
}
As we can see by viewing the source code of ViewModelProviders, of() is basically an extension function of ViewModelProvider that returns a new NonNull ViewModelProvider object with the parameter fragment/activity and locks it on the MainThread. Basically it's a fancy way of writing ViewModelProvider(fragment) with extra steps.
But be aware that of() is deprecated, you now initialise a ViewModel like this:
ViewModelProvider(requireActivity(),ViewModelFactory(Database.getDatabase(requireActivity()))).get(ViewModelClass::class.java)
ViewModelProviders.of(this).get(RandomViewModel::class.java)
ViewModelProviders.of(this)
It is a static function that takes current context to retain the ViewModel scope. In this case Current activity is gonna be the context for which ViewModel scope will be retained.
.get(ViewModel::class)
It does two things
If the ViewModel is available it will return the ViewModel instance.
Otherwise, it will create and return the new instance.
The of() method here is a method inside the ViewModelProviders class which just creates a ViewModelProvider object, which retains ViewModels while the scope you have given eg Activity, Fragment is alive.
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.