Let's imagine the following case: we have BottomNavigationView with Navigation Component and MVVM architecture.
In which case in Fragment this line of code will now work
onViewCreated()
viewModel.isActionDone.observe(viewLifecycleOwner) {
doReaction()
}
but this code will?
private val observer = Observer<Boolean> {
doReaction()
}
onViewCreated()
viewModel.isActionDone.observe(this, observer)
In my app, the second code works great, but the first code doesn`t work correctly: when I move to another element in the BottomNavigationView, the observer is called several times.
P.S. this code also calling observer several times.
viewModel.isActionDone.observe(viewLifecycleOwner, observer)
Сould you please help me understand what could be wrong with the first code?
it's because every time onStart() is called on your fragment, the livedata becomes active again and emits the last value again. you should use the Event class as a wrapper or you could use the SingleLiveEvent class which is now considered an anti pattern.
you could read this for more information: https://medium.com/androiddevelopers/livedata-with-snackbar-navigation-and-other-events-the-singleliveevent-case-ac2622673150
It's because When you move to another fragment, NavController destroy the view and stop the fragment and when you back to the first fragment, fragment will be started and view will be created again.
So when you're using "viewLifecycleOwner" for observe, your LiveData emit the last value each time when you back to the first fragment. but when using "this", it does not.
So you should think about your business that you need have emits each time your view created or each time fragment created and use suitable LifecycleOwner.
I home it help you.
Related
I am following the one-single-activity app pattern advised by Google, so if I want to share data between Fragments I have to share a ViewModel whose owner must be the parent Activity. So, the problem becomes because I want to share data between only two Fragments, independently from the others.
Imagine I have MainFragment, CreateItemFragment and ScanDetailFragment. So, from first one I navigate to CreateItemFragment in which whenever I press a button I navigate to ScanDetailFragment in order to scan a barcode and, in consequence, through a LiveData object inside the ViewModel I can get the scanned value back into the CreateItemFragment once ScandDetailFragment finishes. The problem becomes when I decide to cancel the creation of the item: I go back to the `MainFragment' and because the ViewModel's owner was the Activity's lifecycle, once I go again into CreateItemFragment, the previously scanned value is still there.
Any idea to reset that ViewModel?
but, aren't Viewmodels also aimed to share data between different views?
No. Each viewmodel should be responsible for one view. The "shared viewmodel" pattern is for cases when you have one large view (i.e., activity) that has multiple subviews (i.e., fragments) that need to share data / state, like the master / detail example in the documentation. It's a convenience for these cases when you need real-time updates amongst the subviews.
In your case, you're navigating between fragments and as such should be passing data through the transitions. This means passing arguments along when starting new fragments and registering for results when they complete their task.
Then each of your fragments is isolated, self-contained, more easily testable and you won't end up with a God-ViewModel that does All The Things™ and turns into a giant mess as you try to jump through hoops accounting for every state it could possibly be in.
You can use callbacks in such cases to share data between fragments. or if you use DB/Sharedpreference/Content provider then you do not have to worry about sharing data each page will fetch its own data from the store(DB/SharedPreference/Contentprovider).
you can also try https://medium.com/#lucasnrb/advanced-viewmodels-part-iii-share-a-viewmodel-between-fragments-59c014a3646 if this guide helps
You can clear LiveData value every time when you go into CreateItemFragment from MainFragment.
Or you can just clear it from the CreateItemFragment in onBackPressed() method.
When you cancel the creation of item,set livedata value to null.then within observer code if(updatedvalue!=null) write your code using updated live data value.in this way you can avoid last updated value.
At the moment (on 2022), the method viewmodel.getViewModelStore.clear(); or onCleared(); is deprecated.
So, if you want to clear data holded by ViewModel or clear value of LiveData, you just need use 1 line code like this:
mainViewModel.getLiveData().getValue().clear();
getLiveData() is my method inside MainViewModel class to return liveData variable
getValue() is defaut method provided by LiveData (MutableLiveData: setValue(), postValue())
If you need to clear data when user press on Back button in Fragment, you can do like the code below & put it inside the onViewCreated method - the method of LifecycleFragment.
private void handleOnBackPressed() {
requireActivity().getOnBackPressedDispatcher().addCallback(new OnBackPressedCallback(true) {
#Override
public void handleOnBackPressed() {
Objects.requireNonNull(mainViewModel.getLiveData().getValue()).clear();
requireActivity().finish();
}
});
}
My project on Git if you want to refer code (it still updated): https://github.com/Nghien-Nghien/PokeAPI-Java/blob/master/app/src/main/java/com/example/pokemonapi/fragment/MainFragment.java
I disagree with #dominicoder. At this link, you can find a Codelab made by the Google team updated to Oct 30, 2021. The shared ViewModel pattern can be used when you need a coherent flow to achieve a specific task inside your app.
This method is useful and a good practice because:
The Jetpack team says that has never been a recommended pattern to pass Parcelables. That's because we want to have a single source of truth.
Multiple activities have been heavily discouraged for several years by now (to see more). So even though you're not using Jetpack compose, you still should use a shared ViewModel along with fragments to keep a single source of truth.
Downside:
You need to reset all the data manually. Forgetting to do so will bring bugs into your app, and most of the time, they're difficult to spot.
My app has an activity on which many fragments are placed for different pages.
Some fragments have a recyclerView that data given via a viewModel.
Now, I would first create an instance of the viewModel in each fragment and put the data in the recyclerView using observer and LiveData, but this caused LiveData observer is being triggered two times.
After that, the idea came to me to create a static instance of the viewModel in the activity class to solve this problem and use it in different fragments. It worked, and LiveData observer triggered once.
Now my question is, is it right to use a static instance of viewModel in MainActivity (and use it from fragments)?
If not, then what should I do to solve the problem(Trigger the observer twice)?
While discovering the MVVM with Kotlin and Android, I'm facing a small problem related to the organization of one of my fragments.
Suppose I have an activity that hosts a fragment and after a navigation (with NavController) the activity host a new fragment, which has multiples subfragments (perhaps through a ViewPager). All of the 3 fragments (the parent & the 2 children) must display precise part of a data. Furthermore the second subfragment has a button that could change the data & this change must update the UI of all the fragments.
Firstly in my mind, I was thinking all the data will be stored inside the parentFragmentViewModel due to the fact that their will be useful for the 3 fragments, but that's where my problem appeared.
How the subfragments's viewModels could handle these data & update it?
My first thought seems to be incorrect, because if we read the viewModel doc, we can see "However ViewModel objects must never observe changes to lifecycle-aware observables, such as LiveData objects."
So, my subFragments's ViewModels can't observe the parent one. I was thinking about sharing the same viewModel between the 3 fragments but I don't know if it's a bad practice or not and I don't know how to do it the cleanest way possible.
How can I resolve my problem?
EDIT
After further research, I tried this solution https://stackoverflow.com/a/53819347/7861724
I created the viewModel inside the parentfragment. Once done, I get it inside my subfragment.
It currently work but I'm not sure if it's a good practice.
Why do you need to observe any lifecycle-aware component? You can create setters for MutableLiveData in your ViewModel if you need to update it from he newly created fragments, this doesn't mean that the ViewModel is observing changes.
val data: MutableLiveData<String>
fun updateData(val newData: String) {
data.value = newData
}
The fragments can actually listen to changes from the ViewModel, but that's fine because in the moment they are destroyed, the observers will also stop observing. That means that you can have your buttons and everything updated with no memory leaks.
So, I have implemented single activity with multiple fragments pattern using Navigation. I used viewmodel for each fragment for non-ui operatios.
The problem is when you navigate using findNavController().navigate(), the fragment is not actually destroyed. Only the onDestroyView is called. So, the fragment's onDestroy never gets called and subsequently the viewmodel doesn't get cleared and so the LiveData observer also remains alive and when i come back to the fragment the observer is created again and so live data is observed twice. Once with the old data that it holds and second with the new data from some operations.
For example,
I have fragmentA and fragmentB
A shows a list and B you can add something which will be shown in the list. maybe fetching new data from api in fragment B to be shown in A.
So, when i go back from fragment B to A the observer gets called twice first with old data then second with the updated data. In the end the list shows correct data but I don't want two observers happening.
I have followed this article
https://medium.com/#BladeCoder/architecture-components-pitfalls-part-1-9300dd969808
and tried to use viewLifeCycleOwner instead of this but that does not help and issue still exists.
I also tried removing observer before observing :
vm.ld.removeObservers(this)
vm.ld.observe(viewLifeCyclerOwner, observer)
still the issue remains.
(I tried removing observer in onDestroyView also, still the issue remains.)
The only work around i found is manually calling the viewmodel onCleared in onDestroyView and clearing the livedata.
In fragment onDestroyView
vm.clear()
In viewmodel
fun clear() = onCleared()
override fun onCleared() {
//do stuff
}
Now, this solves my issue. But i feel this is not a solid solution and there can be a better way to do this. I would be glad if anybody can shed a light on this one. Thanks.
I wasted a couple of days troubleshooting a similar issue. Double check how your VM is initialized:
val myVm: MyViewModel by activityViewModels()
vs.
val myVm: MyViewModel by viewModels()
If you use the by activityViewModels() delegate, you are instructing Android to tie the lifetime of the VM to the host activity rather than the current Fragment. Thus, your VM won't be cleared even when the fragment is destroyed. I learned this the hard way. Switching back to the by viewModels() delegate leaves the VM scoped to the lifetime of the Fragment. When the fragment is destroyed, if it's the only observer, the VM will clear.
I was totally confused by the this vs. viewLifecycleOwner target when observing. Apparently, the choice of target has only to do with whether you intend to manually control presenting the dialog of a DialogFragment. Another gem of confusion.
In your case, if you're switching between fragments and onDestroy isn't being called, it could also be because fragments are being retained. For example, ViewPager2 has offscreenPageLimit, which instructs Android to keep hidden fragments in memory as you switch pages, adding even further to this mess of having to know absolutely everything about everything just to use the SDK.
You can use viewModelStore.clear() in your fragment, and then override onCleared() in ViewModel for dispose what your need
You can set the value of your livedata to null in onDestroyView.
example :
override fun onDestroyView() {
super.onDestroyView()
vm.ld.value = null
}
or
in ViewModel
fun resetLiveData() {
_ld.value = null
}
in Fragment
override fun onDestroyView() {
super.onDestroyView()
vm.resetLiveData()
}
We utilize LiveData's observe method quite a bit in our fragments. A recent release of the androidx fragment sdk is leading to Android Studio marking instances of liveDataObject.observe(this) as incorrect in favor of liveDataObject.observe(getViewLifecycleOwner()) .
Added a new Lint check that ensures you are using getViewLifecycleOwner() when observing LiveData from onCreateView(), onViewCreated(), or onActivityCreated(). (b/137122478)
https://developer.android.com/jetpack/androidx/releases/fragment
We are worried about implementing this change because we do not understand how the functionality of getViewLifecycleOwner() compares to that of using this, and whether it will cause a conflict with the use of this or this.getActivity() when setting up the ViewModel in the fragment.
Additionally, we use the Android Navigation component and noticed that when the user navigates to different fragments within the same activity, each fragment's onDestroyView() method is called, but not onDestroy()
Here is an example of our code in onViewCreated()
vm.getStemLengths().observe(this, stemLengths -> {
this.stemLengths = new ArrayList<>(Stream.of(stemLengths).map(stemLength ->
new SearchModel(Integer.toString(stemLength.getValue()))).toList());
});
Later, in onDestroyView()
vm.getStemLengths().removeObservers(this);
At the same time, depending on the fragment, the ViewModel that houses the LiveData is set up with one of the following:
vm = new ViewModelProvider(this.getActivity()).get(PrepareVM.class);
To persist the viewmodel across fragments in the activity.
Or:
vm = new ViewModelProvider(this).get(AprobacionVM.class);
If the VM does not need to be persisted outside of the current fragment
So to summarize, will changing this to getViewLifeCycleOwner() when observing LiveData objects in fragments' onCreateView() conflict with the ViewModel patterns / Navigation component? Could there be an instance for example where the livedata change ends up triggering an observer from a previous fragment in the same activity that a user navigates away from?
From the documentation of getViewLifeCycleOwner it seems that making this change may allow us to remove the removeObservers() call in each fragment's onDestroyView(). Is that the correct understanding?
Fragment implements LifecycleOwner which maps its create and destroy events to the fragment's onCreate and onDestroy, respectively.
Fragment.getViewLifecycleOwner() maps its create and destroy events to the fragment's onViewCreated and onDestroyView, respectively. The precise sequence is described here.
If you're working with views in your observers, you'll want the view lifecycle. Otherwise you might get updates when the view hierarchy is invalid, which may lead to crashes.
From the documentation of getViewLifeCycleOwner it seems that making this change may allow us to remove the removeObservers() call in each fragment's onDestroyView().
Correct.
There is already a bug with it. If you do a new Intent for a fragment already use it gives getView is null error. Why? The activity is newly creared.