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.
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
.
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.
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.
The design described above doesn't seem to be correct. I can point out some:
MutableLiveData
in the ViewModel
;DataRepository
;SharedPreferences
.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.Is that the correct solution? If not, which I believe it isn't, how should be designed the correct solution?
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.
In fact you can have multiple view models for a single fragments doing different things for you.
- 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With