Shared viewmodel between fragments, without scoping to the activity? [duplicate] - android

This question already has answers here:
Scoping a viewmodel to multiple fragments (not activity) using the navigation component
(4 answers)
Closed 3 years ago.
Using the new Navigation Architecture Component, I've got a single activity as a navhost with multiple fragments for my screens. Right now I have an EditProfileFragment where the user can click a button and another fragment opens, with a list of countries to choose from. Let's say I want to share the result of that country selection back to the EditProfileFragment. The general idea is that I'll have a single EditProfileViewModel for all "edit profile" actions.
How do I share the selected country between those fragments? I'm thinking using a shared viewmodel, but I'm hesitant scoping it to the activity because I don't want it to persist when the user completes the "edit profile" flow.
Are there any other clean/recommended approaches I should consider? Maybe a singleton that temporarily holds that value?

It's easier with a shared view model indeed, but as you said, it comes with other concerns like scoping the view model to a higher context for simple information exchange.
IMHO shared view model is not a bad approach in certain scenarios. I was working on an app which has 5 tabs, first tab was like a summary of 2nd and 3rd ones. It was a good choice to use shared view model, since I was just reusing the data, just changing the number of items adapter shows in corresponding views, logic was being reused.
It sounds like you have common logic / items in your profile & profile edit page. I don't know how many, but if you feel like it's not enough to share a view model between these two, remember just because you are using view models doesn't mean you have to use them to share / store / pass around some data. For example :
Navigate to previous fragment with acquired data.
You can save "profile" to persistence and change what's stored. When your view model for profile is (re)created, it gets the latest value from persistence.
You can update your profile in server directly, and fetch it again on profile.
You can mix these two above.

Answering my own question on how I resolved this for future reference:
Because I wanted to keep the 1-to-1 relationship between ViewModel-View(controller)/Fragment, I went with a UserRepository that holds a "temporary state" object for cases like this.

Related

Multi state model in MVI pattern

I understand that the advantage of the MVI pattern is that it is a single-state flow. So is it really necessary to have only one state model in MVI?
My app has several activities, and the subject of data obtained for each activity is completely different. For example, activity A gets the dog's information, and activity B gets the information of a Github user. In this case, if MVI-pattern should be only one state model, the mvi state model contains all the data information of activities A and B?
In most cases we're talking about single-state per ViewModel. And ViewModel is (usually, but not always) bound to a single screen. There is absolutely no incentive to store whole application state in a single object, that would get out of hand really quick
Adding to the accepted answer, Mvi is one of the presentation patterns, which addresses and try to solve the presentation problems. so by presentation we mean Single Screen whether it's an Activity, Fragment or Ui controller.
So with screen A, you'll have uni directional flow to get the Dog's information, and another one to Screen B, and so on.

Reset/clear viewmodel or livedata

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.

Passing model between destinations

I'm working on application which uses Jetpack Compose with Jetpack Compose Navigation. In one view (destination) I'm displaying list on entries (let's call it View A with Model A). From this view user can go to creation view (View B with Model B), where new entry can be created. After successfull creation, I want to update list in Model A, so user don't need to refresh View A after going back to see newly created entry.
Is it possible to pass ViewModel class between navigation destinations using NavHost like this or in any other way?
As per the Thinking in Compose guide:
your composables are responsible for transforming the current application state into a UI every time the observable data updates.
That application state is the source of truth. This matches the Guide to app architecture, where your state is owned by lower level components that are responsible for the actual fetching, storing and caching of data, which is then exposed to the UI layer. This layer responsible for fetching, storing, and caching data is often called the 'repository layer'.
That means that directly passing snapshots of data between destinations in your navigation graph is exactly the wrong way to approach the problem: it creates a source of truth problem (do you trust the snapshot you sent between destinations or the repository?). The answer is always the same: your repository should always be the source of truth and you should never be passing snapshots of data between destinations. In this way, every screen that uses the repository as its source of truth automatically has the most up to date information and there is never a need to 'refresh' your data.
So your architecture would include three layers:
A single repository that owns your list of entries. The most simple part of this may just be the list held in memory as a mutableStateOf<List<Entry>>() that you update when the data changes with a new list. This class would be responsible for talking to the server, caching locally, etc.
(optionally, and a best practice) a layer of ViewModels, one for Screen A and one for Screen B that expose only the sets of methods from the repository specifically needed for that screen (i.e., your ViewModel A might expose a getEntries(), while ViewModel B might expose a createEntry(Entry) method.
Screen A and B focus solely on displaying the data retrieved from their associated ViewModel. As both are talking to the same repository layer, Screen B creating an entry will update the list that Screen A will retrieve its data from.

When using MVVM on Android, should each Activity have one (and only one) ViewModel?

On MVVM pattern, the ViewModel contains business logic and notifies the View when it needs to be updated. It is also notified by the view about user events.
As I understood it, each Model should have an associated ViewModel. So, if we have the following models:
User
Account
We would have the following ViewModels:
UserViewModel
AccountViewModel
However, all examples I find about data binding with MVVM, use a single ViewModel for a layout. And recently, Google has introduced the ViewModel class within Architecture Components. This leads me to believe an Activity would have a single ViewModel that would connect to all related Models:
User / Account --> ActivityViewModel
This gets even more complicated if we think of a RecyclerView. Each adapter item could be a ViewModel itself, so an Activity with a RecyclerView would have multiple ViewModels within the list and plus a master one for the remaining view contents (assuming they require information from a ViewModel). For instance:
In this example, we have a list of Account ViewModels and one UserViewModel. How would this be embedded into a single ActivityViewModel?
You should have one ViewModel per View (Activity, Fragment or Custom View), with multiple LiveData, one for each logical unit. In the image one logical unit would be the user data, another logical unit would be the settings data, so you would have two LiveData exposed in the ViewModel.
These concepts can also be visible in the recommended app architecture google presented in the last Google I/O, where an Activity/Fragment has 1 ViewModel with multiple LiveData:
Google suggests that you use 1 ViewModel per View (i.e., Activity or Fragment) (see https://youtu.be/Ts-uxYiBEQ8?t=8m40s), and then inside each ViewModel you can have more than 1 type of Model. However, the MVVM principle is to have only 1 Model type per ViewModel, so Google's presentation contradicts that :/. I guess you'll have to decide what approach works better for your app.
About the list example you mentioned, that is not how you'd do it, for lists you'd use the paging library. You can see details on how to use this at the end of the video I linked above.
One view model is standard. However, even google suggests that you may have more than one view model. That comes quite convenient when you mitgrate a phone app towards a tablet app. When you combind more than one phone views on a single tab view, then it is convenient re-using the models.
If you have the SOLID principles in mind when coding, then the S (single responsibitly of a class) may cause you to use more than one view model.
However, one per view is pretty much standard and you shold have reasons, as metioned above, to use more than one.
What is the relationship between Users and Accounts? If these are two separate, unrelated models, then they should each have their own views and view models. Remember the single responsibility principle: each module should be responsible for only a single part of your logic. That way, any changes to your domain logic or models will only affect that part, and that part only.
In general it is okay to have 1 View with multiple ViewModels.
View - answers the "how", how should a UI rendered. But it should never answers the "when".
ViewModel - answers the "when", it's the presentation logic and tells when should the view be rendered. ViewModel should never have reference to any View. Instead View observes and listen on ViewModels.
Model - answers the "where", it's the business logic which handles data (local/remote) request. Where to read data and where to write.
As long as ViewModel do not have any reference of View, it should be okay since we can have a situation with Fragment that has its own ViewModel and another ViewModel which comes from its host Activity. We also have a so called shared ViewModel which uses by activityViewModels() that can be use by the Fragments inside the Activity.
You will have a Fragment that has 1 ViewModel own by the fragment itself and 1 ViewModel own by the host Activity.

Android - Create Application level fragment

For my current project, I will be using this SlidingUpPanel library.
The sliding up panel will host a fragment that will contain application/global level information. This panel will be in every activity in my application. This will be very similar to the way Google Play music works.
My question is, what is the best way to persist the sliding up panel fragment throughout my application? I see myself going about this in two ways...
Storing the fragment inside the Application class and loading it at the start of every activity (somehow, not even sure if this is a possibility).
Storing the data that it will display in the Application class & loading a new instance of the fragment, passing in the persisted data.
Which one of these two ways would be the best? Is 1. even possible? Are there any coding landmines with these approaches?
Storing a fragment to persist throughout the application lifecycle would be pretty unorthodox, mainly because the fragment class should be able to follow it's normal lifecycle events (in this case, mainly onPause and onResume) instead of being stuck somewhere in the application class.
It is definitely common practice to store the data and load it each time you display the fragment. If you want to enable some sort of cacheing or singleton pattern to access the data, it should most likely be with another object that the fragment can access but is not a member within the fragment.
There is a good video from google about leaking views and it touches briefly on the pitfalls of doing some similar to what you're proposing in bullet #1.
I think the structure of your app looks like it should be a single activity where that bar is in it, then the main content is a Fragment that you replace and use addToBackStack on in order to maintain the use of the back button. Otherwise, you are going to have a lot of repeated code with solution 2 (which means a lot of repeated work in the case of bugs etc., not very maintainable), or leak views using solution 1.
More info on providing a proper back implementation

Categories

Resources