Briefly, the question is: in MVVM (AAC), how can Domain (business logic) manage the display of complex states / data in the View layer?
Now in more detail.
It means that inside the Domain: 1) received, calculated some data that need to be shown; 2) the state has changed, it is necessary to react to this (hide / show a group of widgets, call a new fragment, show / update progress, etc.). And it's harder to do than just show a message or a dialog, or just send LiveData to the RecyclerView.
Therefore, examples like "hello world" or "2 + 2 = 4" do not fit, everything is clear in them. In MVP, this is simply done. But here I was able to find the weak point of MVVM.
Now I did the following.
By means of RxJava2 (as an option, it can be LiveData from AAC) from Domain to View (via ViewModel AAC) an object that contains the type of command (enum) is passed and has a bunch of fields for data for all occasions (different fields for different commands of course ).
And further, View contains a large switch-case, depending on the type of command where all this is handled.
Variant 2. To create a bunch of specific objects, and then in the View will sit a large if-instanceof.
Variant 3. Store data for View in ViewModel AAC (for which it is actually intended), and send from the Domain only the type of command, then View takes all the necessary data from the ViewModel.
Variant 4. A heap (in case of complex UseCases) a specific Observables in Domain and a heap of subscribers in the View.
So: is there (if any) a more elegant way? There may be some architecture pattern. Maybe I'm in vain reflexing, and this is the right way(s).
ps. 1) the "Command" pattern here does not exactly fit, 2) the "State" pattern has already been implemented by me, and it does not solve the problem either.
In MVP, this is simply done. But here I was able to find the weak
point of MVVM.
This is not the weak point of MVVM, it is just a difference between the implementation of MVP and MVVM.
In MVP, you create a bunch of interfaces to let View and Presenter talks to each other;
In MVVM, you create a mediator (e.g. LiveData) to bridge View and ViewModel.
IMHO, you can:
In your UserCase, create a MediatorLiveData A to store the result.
In your ViewModel, create a MediatorLiveData B to observe A (i.e. MediatorLiveData.addSource(A))
In your View, observe B to reflect any UI updates.
You can find a concrete example in iosched18.
Model View ViewModel architecture
The view is the user interface, the layout. In Android, this usually means an Activity, Fragment or ViewHolder and its corresponding inflated XML layout file.
The model is our business logic layer, which provides methods for interacting with data.
The view model acts as a middleman between view and model, by exposing the data from the model via properties and containing the UI state. Also, it defines commands which can be called on events like clicks. View models contain the presentation logic of your app.
In the MVVM architectural pattern, the view and the view model mainly interact with each other through data binding. Ideally, the view and view model should not know about each other. The bindings should be the glue between the view and view model and handle most of the stuff in both directions. In Android, however, they can not really be independent:
you have to save and restore state, but the state is now in the view model.
you need to tell your view model about lifecycle events.
you might encounter situations where you need to call view methods directly.
For these cases, both the view and the view model should implement interfaces, which are then used for communication via commands, if necessary. In almost all cases, however, only an interface for the view model is needed, since the data binding library handles the interactions with the view, and custom components can be used e.g. when a context is needed.
The view model also updates the model, e.g. by adding a new element to the database or updating an existing one. It is also used to fetch data from the model. Ideally, the model should also notify the view model of changes, but this depends on the implementation.
Now, generally speaking, the separation of view and view model makes the presentation logic easily testable and also helps with maintenance in the long run. Together with the data binding library, this means less code and cleaner code.
Example:
<layout xmlns:android="...">
<data>
<variable name="vm" type="pkg.MyViewModel" />
</data>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="#{vm.shouldShowText}"
android:text="#={vm.text}" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="#{vm::onButtonClick}"
android:text="#string/button"/>
</FrameLayout>
</layout>
When you want to use MVVM architecture, your layouts should only reference one variable, the specific view model for this view, in this case, MyViewModel. In the view model, you provide properties for the layout. This can be as easy as returning a String from a model object or more complex, depending on your use case.
public class MyViewModel extends BaseObservable {
private Model model = new Model();
public void setModel(Model model) {
this.model = model;
notifyChange();
}
public boolean shouldShowText() {
return model.isTextRequired();
}
public void setText(String text) {
model.setText(text);
}
public String getText() {
return model.getText();
}
public void onButtonClick(View v) {
// Save data
}
}
Here we have a text property. As we have an EditText for user input, we can use two-way data-binding, to also have the data binding library save the inputs back to the view model. For this, we create both a setter and a getter and bind the property to the text attribute of our EditText, but this time with a = sign before the bracket, which signals the library that we want two-way data binding here.
Also, we only want to show the EditText when our model says that text input is required. For this, we provide a boolean property in our view model and bind it to the visibility attribute. For this to work, we also have to create a binding adapter, which sets the visibility to GONE when false and VISIBLE when true.
#BindingAdapter("android:visibility")
public static void setVisibility(View view, boolean visible) {
view.setVisibility(visible ? View.VISIBLE : View.GONE);
}
Finally, we want to store the information when a Button is pressed. For this, we create a command onButtonClick() in our view model, which handles interacting with the model. In the layout, we bind the command to the onClick attribute of the Button via a method reference. For this to work directly, our method needs to have a single parameter of type View, just like an OnClickListener. As an alternative – if you don’t want the View parameter – you could also use lambda expressions directly in the layout. As you can see, it’s quite easy and straightforward to use data binding with a view model.
Now, it’s important to remember that we want to put presentation logic in our view model for testability. Avoid putting logic directly in the bindings, even though the data binding library allows it. Don’t forget that you can also have custom binding adapters, which can often simplify things.
I realized that I need to migrate in the direction of the MVI pattern.
And it is necessary to implement "unidirectional data flow".
Very well this solution is described here Reactive Apps With Model-View-Intent - Part2 - View And Intent by Hannes Dorfman
Another interesting solution is the library RxPM -- Reactive implementation of Presentation Model pattern in Android
Related
In MVVM architecture, should the view model only return Live data to fragment?
Is it ok to return other primitive data type other than Live data also?
You could return the data type that you need but in most cases you need to return Live data or StateFlow ( which is similar to Live data )
Because you want that observe in your fragment to get notified about any change.
There is no difference is term of architecture between LiveData<String> and String but it's just about what you need, if you need data that you can observe and change the UI of your fragment depending on that data use Live data and if you just need to access another primitive data type for some reason, it's totally fine.
It depends on your case.
As we know ViewModel is the recommended UI-State holder, it maintains the state while changing the view configuration.
We usually use observable types LiveData, RxJava2, and Flow, so if any changes come from the data or domain layers or even from another view like "SharedViewModel" the UI will be continuously updated.
In most cases, we use observable types in the view model and non-observables in the view itself.
We can see this UI event decision tree in the documentation.
which helps us to configure where should we put the data.
.
for more details.
UI events
State holders and UI State
Whose role is processing data in the MVVM pattern?
For example, if you need to display a Date object in the format mm/dd/yyyy on one screen and mm-dd-yyyy on another screen, who's the role of View or ViewModel?
If this is the role of View I use #BindingAdapter,
If it is the role of ViewModel, use Livedata<Date>().map {/* format */ }
What's the better way to the MVVM pattern?
Thank you.
The view model of MVVM is a value converter, meaning the view model is responsible for exposing (converting) the data objects from the model in such a way that objects are easily managed and presented. In this respect, the view model is more model than view, and handles most if not all of the view's display logic.
So conceptually and implementation wise the logic or transformation should be in View Model.
Better in ViewModel. If you switch from DataBinding in the future, your mapping logic will persist in the ViewModel.
I would keep the original "long" timestamp inside the viewmodel and then decide based on requirements where to transform the data into string. One good point to keep the string inside the viewmodel is that it does the transformation only once, and keep the data until lifecycle ends. While the binding adapter will do the conversion everytime you bind the view. At the same time the string will be kept in memory until lifecycle ends.
Using Android Jetpack components and MVVM architecture, we can get live data updates in a View from a View Model in 2 ways, one is to bind the layout with the live data variable, other way is to observe the variable in code.
To illustrate my question I have taken an example. Suppose there is a view model interface getTimeString() which returns the current time.
a) Layout Data Binding
The view in the layout looks something like this
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
...
app:binder_data_date="#{sampleVM.timeString}"/>
The binding adapter looks something like this
#BindingAdapter("binder_data_date")
public static void binder_data_date(TextView text, String data) {
text.setText(data);
}
b) Manual Data binding (just to give it a name):
In Manual data binding, there is nothing in the layout view with respect to the binder, and I observe the live data using the observe() and update the textview.
FragmentSplashScreenManualBindingBinding fragmentSplashScreenBinding;
SampleViewModel sampleViewModel1 = ViewModelProviders.of(this).get(SampleViewModel.class);
public void onSomeRandomFunc(...) {
....
sampleViewModel1.getTimeString().observe(getViewLifecycleOwner(), data -> {
fragmentSplashScreenBinding.sampleText.setText(data);
});
}
I know the first method is much easier to use and both works.
But is using the second method and the way to access the variable (fragmentSplashScreenBinding.sampleText.setText()) in fragment to update the View correct?
Will the performance get impacted if I use the second method?
Your manual Data binding is not incorrect and doesn't have a significant impact on the performance but you will lose two benefits:
Null pointer exception handling: Layout Data Binding handles null data and you don't need to check null objects to prevent app crash when you want to extract data objects and pass them to views.
Code Reusability: If you want to use your layout in different Activities, with
Layout Data Binding you just need to pass the data variable to the layout. But for Manual Data binding you should copy the same code for each java class to assign variable to views which will make a lot of boilerplate code in complex views.
Moreover, If you are using data binding to replace findViewById() as your second method there is a better way called View Binding which you can read more about it here.
Instead of answering your 2 points in post directly - let me mention few key features of both data binding and Live data - which may eventually help you choose 1 over the other.
Live data class supports Transformations - this useful class provide a way to apply any changes to be done to the live data object before dispatching it to the observers, or you may need to return a different LiveData instance based on the value of another one. Below is a sample example of applying the Transformation on LiveData from Official android docs,
class ScheduleViewModel : ViewModel() {
val userName: LiveData
init {
val result = Repository.userName
userName = Transformations.map(result) { result -> result.value }
} }
As you can see from above example - in the "init" the LiveData Object is "transformed" using Transformations.map before dispatching its content to "observers"
Data binding is mostly works with set of Observables and cannot "transform" the data under observation before dispatching like in above example.
Another useful feature of with LiveData is a class called MediatorLiveData - this subclass which may observe other LiveData objects and react based on changes to it - With data binding AFAIK its very much restricted to a specific Observable Fields.
I am a little confused about how to combine 2 techniques in android, namely
ViewModel (https://developer.android.com/topic/libraries/architecture/viewmodel) and
Data Binding Library (https://developer.android.com/topic/libraries/data-binding)
ViewModel should handle business logic, the layer behind the actual view and send data to the view with something like LiveData. The view observes to this LiveData and updates itself on changes
Data Binding Library exists to make it easier to bind to the view and interact with the view on another level (for example by updating some properties of some class)
The questions:
Should the properties / model property of Data Binding Library be kept inside of ViewModel class (A) or in the view (activity, fragment) (B)
If (A) : If the Data Binding Library properties / models are kept in ViewModel class, is it considered bad practice that view logic is executed inside ViewModel by changing data from the data binding library?
Is there a good code example (some GitHub repo) where there is an example of a decent combination of those 2 concepts?
Update: Found official documentation for my issue. Here is the link:
https://developer.android.com/topic/libraries/data-binding/architecture#viewmodel
How data binding works
Consider using LiveData, it lives inside the ViewModel and is how the data binding library knows that you must update for example the string of a TextView.
What data binding actually does is something similar to what you would explicitly do in your fragment:
Subscribe from your Kotlin code (Fragment/Activity) to a LiveData property that lives within the ViewModel but in this case, data binding will update the view values for you since you will indicate it before from your XML Layout.
So the answer is (A):
You could have a ViewModel class with properties of type LiveData<T> and from your Layout, you can use them directly without subscribing explicitly from your kotlin code as I mentioned before, which continues to guarantee that the ViewModel continues being the provider of information for the user's view, the difference is that instead of you are doing it explicitly, data binding will do it for you.
class MyViewModel : ViewModel {
// view model doesn't know if Fragment/Activity is using data binding or not, it just continues providing info as normal.
val myString : MutableLiveData<String> = MutableLiveData()
init {
myString.value = "a value that data binding will print in a TextView for you"
}
private fun changeMyString() {
// Change the value in the future when you want and then data binding will print the text in your TextView for you.
myString.value = "other value to that TextView"
}
}
Layout:
<TextView
android:text="#{myViewModel.myString}" />
Resources
This Google Codelab is pretty useful, it helped me when I started with data binding because it is prepared to teach.
If you just want to go directly to code, android/sunflower is a repository that uses data binding and in general provides useful samples of jetpack features.
I understand that ViewModel in the Architecture component is for storage and managing data so it will not be lost during config changes.
For example, my activity has nothing do with LiveData or using storage Data. Should I still go through ViewModel? or directly instantiate the Repo Class and call the insertion method? I hope that make sense
An Example of my usage of ViewModel
public class MainViewModel extends AndroidViewModel {
private DataRepo dataRepo;
private LiveData<List<Group>> groupList;
private LiveData<List<Bill>> billList;
public MainViewModel(Application application) {
super(application);
dataRepo = new DataRepo(this.getApplication));
groupList = dataRepo.getGroup();
billList = dataRepo.getBill();
}
public LiveData<List<Group>> getGroupList() {
return groupList:
}
public LiveData<List<Bill>> getBillList() {
return billList:
}
public void insertGroupAndMember(Group group) {
dataRepo.insertGroupAndMember(group);
}
public void insertBills(List<Bill> bills) {
dataRepo.insertBills(bills);
}
public List<Member> getMemberList(Group group) {
return dataRepo.getMembers(group);
}
}
I think you want to use a ViewModel to keep your UI controller as clean as possible. Your viewmodel should call the repo to do simple CRUD operations.
See below snippet from documentation:
Requiring UI controllers
to also be responsible for loading data from a database or network
adds bloat to the class. Assigning excessive responsibility to UI
controllers can result in a single class that tries to handle all of
an app's work by itself, instead of delegating work to other classes.
Assigning excessive responsibility to the UI controllers in this way
also makes testing a lot harder.
Here are some points I would advice you to consider:
MVVM as a pattern has it's roots back in 2000-th, for example, here is Martin Fowler's article about the concept, and in 2005 John Gossman announced the pattern in his blog. Yes, it solves the problem with rotation in android's implementation of the pattern, but this problem could be solved without it. MVVM is actualy needen for separation of presentation state from views that are seen to the end user. As Wiki says -
The view model is an abstraction of the view exposing public properties and commands. Instead of the controller of the MVC pattern, or the presenter of the MVP pattern, MVVM has a binder, which automates communication between the view and its bound properties in the view model. The view model has been described as a state of the data in the model.
So, primarily it is (like all other GUI architectural patterns in their root) about abstraction between view and domain parts of the application, so that they can vary independently and subsequent changes to the system will be cheap.
Instantiating domain objects in the view scope and their subsequent use by the view leads to tight coupling between the view and domain objects, which is a bad characteristic of any system. Also, it is one more reason to change view's internals, because if construction logic of the domain object changes - view will have to be changed too.
If ViewModel is exessive for you (as I can see, its benefits are not relevant for you in this particular case, because the UI is not very complex and it's state is lightweight), consider using a less heavy-weight abstraction, such as MVP. Thus, you will be able to preserve abstraction between view and model in your application (no unwanted coupling) and you won't have to support the code that you don't benefit from.