📜 ⬆️ ⬇️

How to stop using MVVM

Two-headed MVVM

At the recent DroidCon Moscow 2016, there was a report on MVVM with the Databinding Library and a report on the Moxy library, which helps to work with MVP. The fact is that over the past six months we have managed to test both approaches on live projects. And I want to talk about my journey from mastering the Databinding Library and releasing a MVVM project in production to understanding why I no longer want to use this pattern .


Dedicated to all those who are hooked by the Databinding Library and who decided to build an application on MVVM - you are brave people!

Databinding library


When I started with the Databinding Library , I was impressed. Those who are already familiar with it will understand me, and for others, this is how the work with this library looks like:



Using the Databinding Library allows you to:



This last point is especially pleasing to me because states have always been a complex topic. If you need to display three states on the screen (load, data, error), that's all right. But, when there are different requirements for the state of elements depending on the data (for example, display text only if it is not empty, or change color depending on the value), you may need a large switch with all possible interface states, or a lot of flags and code in the methods of setting values ​​to elements.
Therefore, the fact that the Databinding Library allows you to simplify working with states is a huge plus. For example, writing in xml android:visibility=”@{user.name != null ? View.VISIBLE : View.GONE}” android:visibility=”@{user.name != null ? View.VISIBLE : View.GONE}” , we can no longer think about when to hide or show the TextView with the user name. We simply set the name, and the visibility changes automatically.


Viewmodel


But, starting to use the databinding more actively, you will get more and more code in xml . And in order not to turn the layout into a dump, we will create a class into which we will render this code. And in xml there will be only calls of properties. I will give a small example. Suppose there is a class User:


 public class User { public firstname; public lastname; } 

And in UI, we want to see the full name and write in xml :


 <TextView android:text="@{user.firstname + user.lastname}" /> 

You don’t really want to see this in xml , and we create a class in which we put this logic:


 public class UserViewModel extends BaseObservable { private String name; @Bindable public String getFullname() { return name; } public void setUser(User user) { name = user.firstname + user.lastname; notifyPropertyChanged(BR.name); } } 

The creators of the library propose to call such classes ViewModel (straight, as in the MVVM pattern, surprisingly).


In the example, the class is inherited from BaseObservable, and in the code it calls notifyPropertyChanged (), but this is not the only way. You can also wrap the fields in an ObservableField, and dependent UI elements will be updated automatically. But I think this method is less flexible and rarely use it.

Now in xml we will have:


 <TextView android:text="@{viewmodel.name}" /> 

Much better, isn't it?


So, we have a ViewModel class, which acts as an interlayer between the data and the view . It deals with data transformations, controls which fields (and associated UI elements) and when updated, contains the logic of how some fields depend on others. This allows you to clear the xml from the code. In addition, it is convenient to use this class to handle events from the view (clicks, etc.).


And here comes the thought: If we already have a databinding , there is a ViewModel class containing the display logic, then why not use the MVVM pattern?


This thought comes inevitably. Because what we have at the moment is very, very close to what the MVVM pattern is. Let's take a quick look at it.


MVVM


In the Model-View-ViewModel pattern there are three main components:



The relationship and interaction between these components we see in the picture:



The arrows show the dependencies: View knows about the ViewModel, and the ViewModel knows about the Model, but the model knows nothing about the ViewModel, which knows nothing about the View.


The process is as follows: ViewModel requests data from the Model and updates it when necessary. Model notifies the ViewModel that the data is there. The ViewModel takes the data, converts it and notifies View that the data for the UI is ready. The connection between ViewModel and View is carried out by automatic data binding and display. In our case, this is achieved through the use of the Databinding Library. With databinding, the View is updated using data from the ViewModel.


The presence of automatic binding (databinding) is the main difference of this pattern from the PresentationModel and MVP patterns (in MVP, the Presenter changes the View by calling methods on it through the provided interface).

MVVM in Android


So I started using MVVM in my project. But, as often happens in programming, theory and practice are not the same thing. And after the completion of the project, I still have a feeling of dissatisfaction. Something was wrong in this approach, something did not like it, but I could not understand what it was .


Then I decided to draw the MVVM scheme on Android:



Consider what the result is:


ViewModel contains fields used in xml for data binding ( android:text=”@{viewmodel.username}” ), handles events android:onClick=”@{viewmodel::buttonClicked}” on View ( android:onClick=”@{viewmodel::buttonClicked}” ). It requests data from Model, transforms it, and with the help of databinding 'a, this data gets into the View.


Fragment simultaneously performs two roles: an entry point that provides initialization and communication with the system, and View.


The fact that Fragment (or Activity) is considered as View in understanding MVP and MVVM patterns has already become a common practice, so I will not dwell on this.


To survive the turns and the re-creation of the Activity, we leave the ViewModel live for the time while the View is being recreated (in our case, Fragment). This is achieved using dagger and custom scopes . I will not go into details, many good articles about dagger have already been written. In your own words, the following happens:



Why does the fragment send itself to the ViewModel using the MvvmView interface? This is necessary so that we can call the commands "manually" on the View. Not everything can be done with the Databinding Library.

If it is necessary to save the state when the system has killed the application, we can save and restore the state of the ViewModel using the fragment savedInstanceState .


That's about how it works.


The attentive reader will ask: “Why suffer with dagger custom scopes , if you can just use Fragment as a container and call setRetainInstance(true) in it?” Yes, you can do that. But, while drawing a diagram, I took into account that you can use an Activity or ViewGroup as a View.


Recently, I found a good example of MVVM implementation that completely reflects the structure I have drawn. Except for a couple of nuances, everything is done very well. See if interested.

Duality problem


Having drawn a diagram and thought it over, I realized that it was not exactly what I was comfortable with when working with this approach. Take a look at the diagram again. See the thick arrows “ databinding ” and “ manual commands to view ”? Here it is. Now I will tell more.


Once we have the databinding , then most of the data we can simply set to View with xml (by creating the necessary BindingAdapter if necessary). But there are cases that do not fit into this approach . These include dialogues, toast 's, animations, actions with a delay and other complex actions with View elements.


Recall the TextView example:


 <TextView android:text="@{viewmodel.name}" /> 

What if we need to set this text using view.post(new Runnable()) ? (We do not think why, we think how)


You can make a BindingAdapter, in which you create the “byPost” attribute, and have the presence of the listed attributes on the element be taken into account.


 @BindingAdapter(value = {"text", "byPost"}, requireAll = true) public static void setTextByPost(TextView textView, String text, boolean byPost) { if (byPost) { textView.post(new Runnable { public void run () { textView.setText(text); } }) } else { textView.setText(text); } } 

And now every time both attributes are specified for a TextView, this BindingAdapter will be used. Add an attribute to xml :


 <TextView android:text="@{viewmodel.name}" bind:byPost="@{viewmodel.usePost}" /> 

The ViewModel should now have a property indicating that at the time of setting the value, we should use view.post() . Add it:


 public class UserViewModel extends BaseObservable { private String name; private boolean usePost = true; // only first time @Bindable public String getFullname() { return name; } @Bindable public boolean getUsePost() { return usePost; } public void setUser(User user) { name = user.firstname + user.lastname; notifyPropertyChanged(BR.name); notifyPropertyChanged(BR.usePost); usePost = false; } } 

See how much you need to do to realize a very simple action?


Therefore, it is much easier to do these things right on View . That is, use the MvvmView interface, which is implemented by our fragment, and call View methods (just like in MVP).


This is where the duality problem manifests itself : we work with View in two different ways . One is automatic (through the state of the data), the second is manual (through calls to commands on the view). I personally do not like it.

State problem


Now I will tell about one more problem. Imagine the situation with the rotation of the phone.


  1. We have launched the application. ViewModel and View (fragment) are alive.
  2. They turned the phone - the fragment died, and the ViewModel lives. All her background tasks continue to work.
  3. New fragment was created, joined. View through the databinding received the saved state (fields) from the ViewModel. Everything cool.
  4. But what if at that moment when the fragment (View) is disconnected, the background process ended with an error, and we want to show toast about it? The fragment (acting as View) is dead, and you cannot call a method on it.
  5. We will lose this result.

It turns out that you need to somehow store not only the View state, represented by a set of ViewModel fields, but also the methods that the ViewModel calls on the View .


This problem can be solved by setting the field flags in ViewModel for each such case. It is not very beautiful and not universal. But it will work.


About states


The problem of states prompted me to think that the state of an object can be recreated in two ways: a set of parameters characterizing the state, or a set of actions that need to be performed to bring the object to the desired state .


Imagine a Rubik's Cube. His condition can be described in 9 colors on one of the faces. And you can set of movements that will lead him from the initial state to the desired.



You may need just one turn, and maybe a lot more than nine. It turns out, depending on the situation, some way of describing the state is better or worse (less data is needed).


Moxy


Considering ways to recreate the state, I could not remember the Moxy library. My colleagues in parallel did the project using the MVP pattern and this library. I won't talk about it in detail, there is already a great article from the authors .


In the context of my reasoning, one feature of Moxy is interesting : it stores the view state as a set of commands invoked in this view . And when I found out about it for the first time, it seemed strange to me.


But now, after all the reflections (which I shared with you above), I think this is a very good decision.
Because:



In addition, this approach gives another plus. It also, like the Databinding Library, in its own way solves the problem of a large number of different states. You also don't have to write a huge switch that changes the UI depending on the set of fields or the name of one of the states, since the changes are recreated by a set of method calls.


And yet I can’t say nothing more about Moxy. In my opinion and the opinion of my colleagues, today it is the best library that helps to work with the MVP pattern. It uses code generation to minimize developer effort. You can not think about the implementation of the pattern, but think about the functionality of your project. And that's good.


But enough about MVP. Still, we are talking about MVVM, and it's time to take stock.


findings


I like MVVM as a pattern, and I do not dispute its advantages. But for the most part they are the same as other patterns, or they are a matter of developer taste. And the main plus gives all the same databinding , and not the pattern itself.


Driven by sympathy for MVVM, I implemented a project on it. He studied the topic for a long time, pondered, discussed and made for himself a set of minuses of this pattern:



Yes, you can get used to these minuses. But after much thought, I came to the conclusion that I do not want to work with a pattern that creates fragmentation of approaches . And I decided that I would write the next project using MVP and Moxy.


Whether you use this pattern - decide for yourself. But I warned you.


PS: Databinding Library


Let's end, perhaps, with the same thing that we started with - the Databinding Library. I still like it. But I am going to use it only in limited quantities:



And that's all. This will give advantages, but will not beckon towards MVVM.


If you also plan to work with the Databinding Library, here is some useful information:



')

Source: https://habr.com/ru/post/312548/


All Articles