Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 970
Mateus Pires Avatar asked Jul 12 '18 13:07

Mateus Pires


People also ask

Can we use single ViewModel for multiple activity?

In android, we can use ViewModel to share data between various fragments or activities by sharing the same ViewModel among all the fragments and they can access everything defined in the ViewModel.

Can one fragment have multiple Viewmodels?

In fact you can have multiple view models for a single fragments doing different things for you.


2 Answers

  • 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.

like image 61
TpoM6oH Avatar answered Oct 22 '22 06:10

TpoM6oH


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.

like image 1
Thracian Avatar answered Oct 22 '22 08:10

Thracian