I'm learning the new Architecture Components for Android programming and I'm having a tough time understanding what the responsibilities for an activity are. I get that they are only supposed to handle UI, which makes sense, but to what extent? Let's say I have an activity where, initially, there's an EditText where the user can enter a String. There's a button below this that adds another EditText for the user to enter another String. How should this data be handled behind the scenes? The two solutions I've come up with are the following:
1) Handle the data in the ViewModel.
User clicks the button -> Activity tells ViewModel -> ViewModel maintains list of EditTexts -> When user is done, Activity tells ViewModel -> ViewModel processes data
The issue I see with this approach is that now there are UI elements in the ViewModel and I would have to handle a state of sorts (The user adds some EditTexts but then cancels the whole operation and the ViewModel would have to clear it's list)
2) Handle the data in the Activity.
User clicks the button -> Activity maintains list of EditTexts -> When user is done, Activity sends list of Strings to ViewModel -> ViewModel processes data
The issue I see with this approach is that now there's logic in the View that would have to be replaced if this View was ever replaced, it's not as "dumb" as it should be.
what's the preferred way of handling this situation?
First of a ViewModel should never include a View such as EditText, as quouted from the ViewModel page: "Caution: A ViewModel must never reference a view, Lifecycle, or any class that may hold a reference to the activity context.". When you do, it might cause a memory-leak so that excludes option 1.
Secondly the ViewModel shouldn't be used for processing data, it is merely a container for the data. Processing should be preformed in the background and the result should be stored in the ViewModel, so that potential listeners can react to the data-change.
So to answer you question: In this case I would create a custom ListView with a custom view that contains a EditText. Our ViewModel will contain MutableLiveData for a String list called entries, like this MutableLiveData<List<String>> entries = new MutableLiveData<List<String>>(); which we can set by calling the setValue(object) method.
When the user pushes the button all the text from the EditText views are combined in a String list and we set entries to the created list with the setValue(object) method. All the listeners to entries, will be providen with the new data. So for example you could kick off a background job for processing the new values, like so: model.entries.observe(this, Observer { ProcessStringListTask().execute(it) }) (note that the code is in Kotlin).
Hopes this answers your question.
Best regards,
Dingenis
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.
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.
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
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.
Let me just say up front that this is more of a "structural" question, and I'm not asking anybody to write code; I'm just trying to figure out how I should be structuring my application.
I'm using Android's DrawerLayout/NavigationView for my app. This means that MainActivity is the host for all my fragments.
I currently have three fragments (in reality it's many more, but they are more or less exactly like these three fragments, just for different sets of data).
ListFragment
DetailFragment
EditFragment (used for both adding and editing)
On my ListFragment I have (surprise!) a list of items. This is a LiveData collection on SharedViewModel (which is tied to MainActivity's lifecycle). When an item is tapped I pass the event through to MainActivity by means of an interface listener.
MainActivity then loads up the DetailFragment. In the same call, I load an instance of SharedViewModel (again tied to MainActivity). I set SharedViewModel.selectedItem to be the tapped item. Then, in DetailFragment's onCreate function, I get the selected item via ViewModelProviders.of(activity).get(SharedViewModel::class.java).selectedItem.
On the DetailFragment, there's an edit button. This goes through more or less the same routine described above, but loading up the EditFragment instead. When the edited/added item is saved, I add/replace the item in SharedViewModel's collection through MainActivity's interface listener.
Obviously this is not optimal for several reasons. For one, it means that I've got at least five large sets of data hanging around for MainActivity's lifecycle (the entire lifecycle of the app, essentially). Also, MainActivity grows way out of hand as I have to add more and more functions to handle events.
What I want to do is have, for example, my list of items on a ListFragmentViewModel which is tied to ListFragment's lifecycle. My selected item on a DetailFragmentViewModel, my editing item on an EditFragmentViewModel, etc.
My problem is that I'm not sure how to properly pass the data around in this case. For example, let's say I add a new item in EditFragment. How do I get that into ListFragmentViewModel's collection of items? ListFragment is in the back-stack, so its viewmodel hangs around and doesn't reload the data when it's navigated back to, since it still has the collection from before. This makes sense and is probably how it should be (after all, who wants to wait for all the data to load when they go to DetailFragment and back to ListFragment?), but it means that I don't get my new item in the collection.
That's just one example, but I'm running into quite a few issues like it (e.g. passing the selected item to DetailFragmentViewModel.)
I'm not quite sure what direction I should even be going here. Can someone more experienced help me out?
let's say I add a new item in EditFragment. How do I get that into ListFragmentViewModel's collection of items?
EditFragment tells your item repository, "yo! here's a new item!". The item repository arranges to update your backing store, plus emits an event to interested parties notifying about the data change (e.g., emits an event on an RxJava PublishSubject). The ListFragmentViewModel listens for those events and reacts accordingly.
ListFragment is in the back-stack, so its viewmodel hangs around and doesn't reload the data when it's navigated back to, since it still has the collection from before
It should be finding out about the data change from your item repository, and doing whatever makes sense to reflect that data change. That could be simply taking data from the data-change event and updating its in-memory content. That could be re-requesting information from the backing store. In principle, it could be something else.