Reset/clear viewmodel or livedata - android

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.

Related

Best way to communicate click events between fragments (using ViewModel)

I was wondering about what is the best way to tell different observers (fragments) that a click event happened, using ViewModel.
I found this article on the web that shows a way, but it is not useful if more than one observer is interested on the click event (because as long as the first observer gets the value, it'll return null for the next observer).
If you are ok with using experimental kotlin features I would suggest SharedFlow
you can expose an instance from your view model (assuming its the same view model instance in each fragment) and they can all observe it.
SharedFlow can be configured so that it only emits when new values are sent or it can give you the current value when you subscribe unlike LiveData which always gives you the latest value which you might not be interested in

How parentFragmentViewModel could communicate with subFragmentViewModel?

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.

Does every view have to have presenter in MVP pattern?

I am working on a small application using MVP pattern. My home activity has viewpager with multiple fragments in it and each fragment has its own presenter. Fragments aren't communicating with each other so the activity doesn't have any logic, it is just initializing fragments on start.
So if I would like to implement the pattern by the book and stay true to its principals should I implement presenter also for the activity? And if so what should be its role?
If you want to implement MVP by the book and stay true to its principals, every UI that has user interaction should have a presenter. In this case, if your activity is not interacting with the user, there is no need to have a presenter, and your fragments can have their own. If your activity needs, let's say show a loading to the user because of some data loading prior to show the fragments (this is a user interaction because you are interacting with the user to let them know that something is happening so they should wait), then might be good to consider having a presenter for the activity.
MVP doesn't care at all about whether is an Activity/Fragment/View, it just knows View which is considered as an abstraction of whatever can be shown to the user :)
That is at least, from the 'rules' perspective. My 2 cents is, be flexible, if you see that it actually ends up adding value to you and your project, do it, otherwise, sometimes you have to 'break' the rules or create your own.
For using the fragments with their own presenters, I try to use the presenter-contract classes duo to manage the UI events in the fragments.
For example, Consider a click event to show a toast message in case of two possible outcomes: 1. Save and 2. Delete
Then, I will declare two view contract methods like this:
interface View{
fun showSaveMessage()
fun showDeleteMessage()
}
And then, in the fragment, I will use an instance of my presenter class to display the messages at appropriate times like: presenter.doSaveAction(), the presenter in turn will cause the view to show the toast message.
Also, when I come to the actual logic of the fragment, like for fetching some data from a remote server, I use Interactor class along with the Presenter-View classes to perform it.
I believe staying true to all the principles is virtually dependent on what kind of application you are building. Sometimes, it is more feasible to use MVVM with MVP than only MVP pattern for the app architecture too.
I hope this answers your question, kind of?

Cannot perform this action after onsaveinstancestate after using liveData for fragments transition

this one question has been bothering me for 6 months, it is like a missing peace.. So, I really like LiveData and use it a lot, perhaps too much. Basically, all our fragments adding and removing is managed by LiveData. I have done it for several reasons:
We need to remove fragments in some cases, after onPause has occurred (odd, but a must for our use case).
We have only a single activity with fragments.
I have created a specific navigationViewModel which is shared across all fragments and is created in activity.
I add, remove fragments in this manner:
//ViewModel
...
val addFragmentNr3 = SingleLiveEvent<Boolean>()
//Activity or some fragment calls this:
navigationViewModel.addFragmentNr3.value = true
Then I observe LiveData in Activity and handling transition:
​
navigationViewModel.addFragmentNr3.observe(this, Observer { response ->
if (response != null) {
if (response) {
router.addFragmentNr3(supportFragmentManager)
}
}
})
Then router handles it:
fun addFragmentNr3(supportFragmentManager: FragmentManager) {
val fragmentNr3 = FragmentNr3()
supportFragmentManager.beginTransaction().replace(R.id.root_layout, fragmentNr3, FRAGMENT_NR_3.commit()}
In my honest opinion this should definitely prevent from this crash:java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
However, it does occur in our crash analytics.. It occurs rarely after more complex logic (like updating livedata after onActivityResult), but it does occur...
My main question is: Isn't it is a case, that LiveData handles such scenarios and would emit results only when it safe to perform operations? If not, it means my logic is bad and this approach is complete failure.
P.S. I would like to use navigation library, but as I said we have to manually remove some fragments after user goes to background, or uses split mode and etc.
LiveData does not know whether an action is safe to perform or not.
onSaveInstanceState() is called sometime before onStop() for Android version below P. So there is a small chance that the observer gets notified after onSaveInstanceState() is called.
According to doc, it turned out that onSaveInstanceState() should mark the lifecycle as CREATED and observers are not supposed to be called after onSaveInstanceState().
Suggestion on how to fix it.
One way is to use Android Navigation component and let it handle all of the fragment transaction.
If this is not feasible--like op's case--I suggests just using .commitAllowingStateLoss().
fun addFragmentNr3(supportFragmentManager: FragmentManager) {
val fragmentNr3 = FragmentNr3()
supportFragmentManager.beginTransaction().replace(R.id.root_layout, fragmentNr3, FRAGMENT_NR_3
.commitAllowingStateLoss()}
Now, if you search on the internet there will be dozens of articles warning how using .commitAllowingStateLoss() is bad. I believe these claims are no longer applicable to modern Android development where view restoration does not rely on saved bundles. If you are building an Android application with view models, you hardly need to rely on the Android framework to do the saving. In a proper MVVM application, the view should be designed in a way that it can restore its complete state based on its view models, and view models only.

Handling input with Android Architecture Components

TL;DR
How do I deal with Activities that actively change data (for example through an EditText)? Do I keep saving their state in the SavedInstanceState on rotation and only use the ViewModel when all of the fields are ready, or is there a way to make the ViewModel responsible for checking/holding/using the UI's data?
Question
I'm developing my application using Google's Arch. Components, and writing my latest class I've noticed I'm not really sure on what the best practice is when handling, say, data coming from an Activity form.
Example
I have a POJO made of title, description, location, type
I have an Activity with four EditText: title_et, description_et, location_et, type_et.
My ViewModel, through a Repository (irrelevant here), can send an object to the Database when the sendObject function is called.
How I'm doing it now
The activity has the mTitle, mDescription, mLocation, mType.
On rotation, the activity saves all of the EditText values in the savedInstanceState bundle, and it loads them again populating the views.
When the user wants to send the object, it clicks a button and the activity calls the function viewModel.sendObject(mTitle, mDescription, mLocation, mType) after the necessary checks.
Problems with this approach
The activity is responsible of holding/checking all the data of the EditTexts, basically making the ViewModel only responsible of interacting with the Repository.
What I'd like to accomplish
Ideally, I'd want to make the Activity only responsible of the UI, delegating everything to the ViewModel.
This way I could call sendObject() and the ViewModel would have already all of the data needed.
The LiveData situation
Right now the ViewModel has only one instance of LiveData, inside that there is a Resource (which is taken from here) and it's used to "tell" the Activity that new data has arrived or an error occurred.
This approach works fine in all Activities that just receive data from the network and display them. What do I do when I want to synchronise data coming FROM the Activity? Do I use one LiveData for each field and use that to display potential errors?
I've read most of the samples but all of the Activities there are passive.
Conclusion
Thanks to anyone who takes the time to help.
You can either separate the logic into a model string class with another class containing all your String values for the edit text fields are just assign the String values at the top of your class.
You can have a LiveData of your model in the ViewModel and alter it from the View (Activity/UI). The downside is that to update the LiveData, you need to copy whole Model, edit it and post it back to live data.
The second way is to dissect Model's components in the ViewModel into individual parameter LiveDatas. Later when form is submitted you can reconstruct the Model.
What you can do for native fields is use data binding. For other you need manually update LiveData from the View with listeners etc.

Categories

Resources