Handling input with Android Architecture Components - android

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.

Related

Android MVVM pattern: where to start activity for result?

In the MVVM architecture, where does one start an activity for result ? For example, I need to get banking informations from an nfc tag in my payment app. What I do is I start the ReadNFCActivity from my PaymentActivity, retrieve the results there, and then I call my PaymentViewModel's updatePaymentInformations(name: String, account_number: Int...) method from my PaymentActivity. The data going from one activity to the other before being sent to the viewModel doesn't feel like the right way. How would you structure this ?
Any help will be appreciated !
In MVVV you shouldn't gather or keep data in your views. In your example you shouldn't get data from the card in the view. You can get the data from the card in the viewmodel and save in a Livedata variable. Whenever you need to display the data you can observe this Livedata. Handling the data this way can help you in complicate lifecycle aware data management scenarios.
Its better to think of Activities as containing a view not the view itself. Meaning showing stuff on the screen is not it's only purpose it is also a/the context of the app which allows access to OS functionality and resources.
Unlike Windows where you can access OS functionality and resources anywhere making MVVM trivial, instead in Android you have to access it through a context, which is the Activity/Application.
IMO
If your activity is starting another activity for a result than its well with its right to do that, because its the context. Passing that result to the view model is no different than sending input from a listener to the view model like text input.
If the view model isn't touch the views than you view model is good to go.

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.

Best way to pass data needed for a later (not following) fragment?

I have an application that needs to collect some data before doing it's main job.
So, the first fragment collects data, the second fragment collects data and then the third fragment uses the data.
The problem is: data in the first fragment is uncorrelated to the data I collect in the second fragment.
How can I pass the data from the first fragment to the third? Should I incrementally pass all the data I collect in the next fragment arguments, or should I store them elsewhere? I'd really like to know what the best practice is.
explicative image
I won't use a database, since I don't need to permanently store the data.
Thank you!
As for any best practices, the best answer is "it depends".
If your app is really small and simple then it's okay to pass data from one screen to another in the arguments bundle.
A more complex approach is to store data somewhere outside of these Fragment lifecycles.
It's a general rule you can implement as you want. A couple of examples below:
Data can be stored on Application class level. Application class runs for all application lifecycle. Every fragment can get Application instance from its activity like activity?.getApplication().
Data can be stored on Activity level if all fragments run in a single activity. Activity can be obtained from Fragment using activity or requireActivity().
Data can be stored on a parent fragment level if all fragments run in this parent fragment. It can be obtained from child fragments using parentFragment.
All these approaches suppose you to cast some "parent" thing to specific interface or implementation. For example, if your fragments run in the MainActivity which is stores some val data: Data as its property, then you should use it something like this: (requireActivity() as MainActivity).data
Clarification on a couple of popular answers:
The Shared ViewModel is just a special case of activity-level approach as ViewModel instance is stored in Activity scope.
The SharedPrefrences like any "database" approach is a special case of application-level approach as SharedPreferences is stored on application-level (in the file storage) no matter where you create its instance. (And SharedPreferences persists data across application starts, so it's not your option)
In addition to mentioned "Shared ViewModel" technique, Androidx introduced new "Fragment result Api" starting with "Fragment" library v1.3.0-alph04 (currently in beta) which could be used for communication between pair of Fragments or Activity-Fragment.
A Fragment/Activity set a listener in FragmentManager by specifying a key and other Fragment/Activity send data (in form of a Bundle) to the listener with that key. If there's no listener for the key, FragmentManager keeps newest data until a listener gets registered.
Pay attention that listener and result must be set on same FragmentManager instance.
I my opinion, its good for signals (events), not for sharing data. A situation I found it useful, was sending "onWindowFocusChanged" from Activity to Fragment. In case of sharing data, Shared ViewModel is better.

Synchronize the UI and data of the recyclerview in the current fragment with the UI and data of the recyclerview in the adjacent fragment

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

How should be implemented the ViewModel for an activity with many fields

Problem
There is a settings screen (SettingsActivity) with about 10 text fields and 3 buttons. The text fields, which on onClick open a dialog to insert/edit text, has its contents saved in the SharedPreferences. The buttons do async requests to retrieve contents and save elsewhere. During the requests, a dialog is shown to notify the progress.
Initial solution
DataRepository
Basically a wrapper for the SharedPreferences that has 10 getters and 10 setters, one for each field. On get[field_name], the DataRepository gets the value from the SharedPreferences and on set[field_name], it commits to the SharedPreferences.
ViewModel
A ViewModel which has 10 MutableLiveData objects, one for each field. This class implements the LifecycleObserver to know about the SettingsActivity lifecycle so it could load the fields from the repository on onCreate and save the fields to the repository on onDestroy.
There are also 3 methods to do the 3 async requests that are fired by the 3 buttons mentioned. Each method receives an OnRequestProgressListener instance, that is passed to the class that makes the async request to be used to notify the view about the progress.
View
An activity with 10 fields, that observes the 10 MutableLiveData from the ViewModel. On onClick of each field, a dialog is opened to edit/insert text. On the onPositiveButton of the dialog, the observer of the corresponding field is called.
The activity implements the OnRequestProgressListener to show and hide dialogs accordingly to the async requests progress.
Initial solution problem
The design described above doesn't seem to be correct. I can point out some:
10 MutableLiveData in the ViewModel;
10 getters and 10 setters in the DataRepository;
A repository for SharedPreferences.
The ViewModel receives listeners to pass to the classes that do the async requests which use these listeners to notify the view. All with the ViewModel in the middle.
Correct solution
Is that the correct solution? If not, which I believe it isn't, how should be designed the correct solution?
10 MutableLiveData in the ViewModel;
That's totally fine, if you have 10 independent pieces of data you can have a LiveData for each one.
A repository for SharedPreferences.
Repository should be an abstraction over your data layer, allowing you to easily switch implementations. So having a repository over shared preferences is ok in theory.
But in your case, if the only thing the repository does is forwarding calls to SharedPreferences since the probability of switching storage solution from shared prefs to something else is very low, I would get rid of the repository and use SharedPreferences directly to simplify the code.
10 getters and 10 setters in the DataRepository;
Same here, if you have 10 pieces of data stored in you class and want it to be accessible from outside you should use the property pattern, that results in a getter and setter in Java. In Kotlin though you won't need to write getters and setters explicitly.
Also, if you decide to remove DataRepository you won't need that code.
The ViewModel receives listeners to pass to the classes that do the async requests which use these listeners to notify the view. All with the ViewModel in the middle.
This one sounds a bit wrong, if you create a listener in your activity chances are that you'll accidentally use the anonymous class that has a reference to activity, pass it to the ViewModel and get a memory leak.
You should not pass activity reference to the ViewModel.
The correct way of communication is through LiveData. You need to create a LiveData that will post progress, use it inside the ViewModel, providing it with the progress and your activity needs to subscribe to it to get the progress information.
With this approach, you'll be safe from memory leaks.
You can use one Settings data class that contains Strings, integers and getters that encapsulates your data.
One MutableLiveData<Settings> is enough to keep track of changes and pass changes to UI. And with data-binding you also won't need boilerplate code if you are using TextViews and EditTexts. For data binding to work with LiveData you need to invoke setValue() of LiveData using a method defined in ViewModel. I made an example how data-binding works and does not work with LiveData.
You can see my answer for how you can work with LiveData with mutable objects and update UI without any boiler code and unnecessary MutableLiveDatas or transforming MutableLiveData<Object> to MutableLiveData<String> or MutableLiveData<Integer>.
If you can share your layout file i can post more specific answer.

Categories

Resources