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.
I create applications using Firebase, Kotlin, Android Studio and Fagments with ViewModels. I'm not using any server.
In one of the Fragments I display a list of friendly users using the viewModel.fetchFriends() function where addListenerForSingleValueEvent() is located, which of course triggered when the fragment is created.
However, I would like the earlier Fragments to show the icon that someone new accepted our invitation and we have a new user in the list of friends.
Can somehow call the fetchFriends() function in an earlier Fragment? or maybe I should declare fetchFriends() in an earlier Fragment and pass the fetched data to target Fragment when navigate?
Which method should be used in these types of situations?
You could use a shared view model that is passed along the various fragments you're navigating.
There's a complete example of this situation in the ViewModel Overview documentation page.
I'm trying to implement the recommended architecture by Google and in a tutorial they show this diagram:
So I have a MainActivity and when the app starts it should go and fetch some data from the internet. I do those network operations in the Repository. Now my problem is that I don't know how to communicate properly between activities and Repository. For example MainActivity starts and immediately display a circular progress bar while Repository fetches the data. How can I stop the animation in MainActivity as soon as the data is inserted to the database? I guess I could call observe() on the LiveData and wait for onChanged(). Is there a better approach? What if there is no new data? Then onChanged() wouldn't be called...
Maybe I could send intent from Repository to MainActivity when there is no data so MainActivity knows it should stop the animation and if it doesn't receive the intent it just waits for onChanged()?
I guess I just don't feel confortable with the onChanged() method because I will never be sure of the operation it corresponds to. Maybe before the data from the network arrived there was some other data inserted which trigged onChanged() which would then stop the loading animation before it was supposed to.
Regarding your issue in the comments, which I believe to answer your main question also.
You need to observe from your UI (Activity / Fragment) to a progress LiveData in your ViewModel. That could be working with a Boolean (LiveData<Boolean>). To represent the progress view being visible or not.
That in turn needs to take an identical LiveData from the Repository (declared in the Repository as a MutableLiveData). You then post updates to the progress MutableLiveData in the Repository.
Now, whenever the MutableLiveData receives a change, that exists in your ViewModel as it shares the variable reference, and it will pass to the observer in your UI.
-
Alternatively, you could return a LiveData<Boolean> from the method in your Repository that pulls data. That would then be observable in your UI.
Instead of Boolean, you could also use a more complicated structure containing more information. A message, error code, etc.
This is the 'like' feature on Facebook.
I would like to synchronize these recyclerviews with these two pieces.
If you click on the 'Like' button on the recyclerview in one piece, the 'Like' button on the recyclerview should change when you change to another piece.
Which method should I use?
interface?
service?
Map Should I use this?
What method do you use to synchronize the data of two fragments?
You should be using ViewModel's from architecture components.
https://developer.android.com/topic/libraries/architecture/.
Basically you create a view model in the activity so that it is stored with the activity scope
//this is the instance of the activity
ViewModelProviders.of(this)
You can then get an instance of this view model in each fragment using
ViewModelProviders.of(getActivity())
The view model can then be used like in a standard MVVM architecture.
Each fragment should register to the lifecycle aware components that the ViewModel would provide. MutableLiveData is one such component that you could use to provide the data back to whoever is interested in the data (in this case each fragment)
Be aware that LiveData while does a fantastic job can be limited as it stores data as a state in time. This is great, but android should be developed where it is driven by events)
As an example If you have a viewmodel which sends data to the view via livedata it could trigger a dialog. When the user closes that dialog and causes a configuration change (destroys and recreates the activity) the view will receive the state of the live data at the point in time it was set which will again show the dialog. Basically each time you rotate the device it could show the dialog agian even though you've dismissed it.
A hacky fix to this is to notify the viewmodel to remove the state in the livedata after the dialog is dismissed. but this creates a number of other issues including tying view state with the viewmodel
It's a lot more flexible if the Lifecycle aware component instead sends events of when data changes. Think Rxjava that is lifecycle aware. You add the data to the RXJava component and the observable provides the data to the observer when the view is in a state to consume it (> onresume and < ondestory).
Hopefully that gives you a starting point. Let me know if you need more details
I am following MVP approach, and I have an activity containing 4 fragments. On App launch activity makes network call, and passes data to fragments.
In one of fragment i have recycler view, and it has Swipe to Refresh.
When user swipe to refresh i need to make network call from fragment, which i learnt is not a good idea, and also i am following MVP approach, so activity has the list of methods to execute in presenter, and presenter decided whether to grab data from local repository or remote.
So what should i do to perform swipe to refresh operation from a fragment.
Should i call activity method, and perform network call from there, and re pass the updated data to fragment, or is it fine to make a network call from fragment.
The answer is neither the Activity nor the fragment. The fragments and activities shall never "fetch" or "handle" data. Only display data. And deal with activity lifecycle with regards to this.
If you want to know further this will be a good read for you.