Let's make an MVVM at Android

Now it's time to make a flexible architecture for Android using DataBinding!

Hi there! First of all I would like to apologize for the 9 months of silence since the publication of article about DataBinding. I did not have enough time to write the promised sequel. But even so there are some pros: this time we’ve managed to «sand» some solutions and make them even better ;)

What is MVVM?

For a start, let's consider the classical description of this template and analyze each of its components. Model-View-ViewModel (ie MVVM) is a template of a client application architecture, proposed by John Gossman as an alternative to MVC and MVP patterns when using Data Binding technology. Its concept is to separate data presentation logic from business logic by moving it into particular class for a clear distinction.

So, what does each of the three parts in the title mean?

  • Model is the logic associated with the application data.
  • In other words, it is POJO, API processing classes, a database, and so on.
  • View is actually a layout of the screen, which houses all the widgets for displaying information.
  • ViewModel is an object which describes the behavior of View logic depending on the result of Model work. You can call it a behavior model of View. It can be a rich text formatting as well as a component visibility control logic or condition display, such as loading, error, blank screens, etc. Also, it describes the behavior that was initiated by the user (text input, button pressing, swipe, etc.).


What does it give us as a result?

  • Development flexibility. This approach improves the teamwork convenience, because while one member of the team works with the layout and the stylization of the screen, the other, at the same time, describes the logic of the data acquisition and data processing;
  • Testing. This structure simplifies test writing and the process of mock objects creating. Also, in most cases it eliminates the need for an automated UI testing since you can wrap ViewModel itself with unit tests;
  • Logic separation. Due to the greater differentiation code becomes more flexible and easy to support, not to mention its readability. Each module is responsible for a specific function only.


Since nothing is perfect, there are some drawbacks:

  • This approach can not be justified For small projects.
  • If the data binding logic is too complex, application debug will be a little harder.

But still, who is who?

Initially, this pattern needs a little modification on Android. More precisely, it is necessary to revise the components and their habitual perception.

For example, let's consider Activity. It has a layout-file (XML) and associated Java-class, where we describe everything about it work. Does it turn out that the xml-file is a View, and java-class, respectively, the ViewModel? Not quite so. What if I say that our class is a View too? After all, custom view also has xml and handler class, but it is considered to be unified. Moreover, you can live without the xml file in both the activity and the custom view, while creating the necessary widgets in the code. And so it turns out that in our architecture View == Activity (i.e, XML + Java-class).

But what is ViewModel than, and, most importantly, where should we put it? As we could see in one of the sections of the previous article, it is a completely separate object. And that is the thing we passed to a xml-file using binding.setViewModel(). It will have fields and methods, which we need to bind models with View.

Model has no difference from the traditional understanding of it. The only thing that I would like to add from myself — do not make reference to a database or an API directly in the ViewModel. Instead, create Repository for each VM — thus the code will be cleaner and less bulky.

This way we get the following: activity "serves" only the logic that relates directly to the View, but does not apply to its behavior. Such cases may include the installation of add-on Toolbar or TabLayout and Viewpager. It is important that only the View can access the widgets directly by the id (binding.myView.doSomething()), as VM does not need to know a thing about the View — communication between them is implemented only with Binding. The data load and display logic is on the ViewModel, and the algorithm for obtaining the data described respectively in the Model.

Let's make an MVVM at Android

Our designer went on vacation, so scheme will be with elements of New Year's mood :)

Just do it!

Now directly to the implementation. Looking at the chart above, we can notice that the View sends ViewModel not only commands (user actions), but also its life cycle. Why? Because particularly it is also a kind of action to be initiated by the user. After all, it is because of his actions a screen changes its state. And after that we need to react on a correct application work. The solution suggests by itself: we need to delegate the necessary callbacks to the VM.

Imagine that you need to download the information each time the user returns to the activity. For this, we need to call data download method to onResume().
Let's change ProfileActivity:

private ProfileViewModel viewModel;
 
@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   ActivityProfileBinding binding = DataBindingUtil.setContentView(this, LAYOUT_ACTIVITY);
 
   viewModel = new ProfileViewModel(this);
   binding.setViewModel(viewModel);
}
 
@Override
protected void onResume() {
   super.onResume();
   viewModel.onResume();
}

...and define the same method in ProfileViewModel:

public void onResume() {
   isLoading.set(this.user.get() == null);
   userRepo.getUser(this::onUserLoaded);
}

Now the data will be updated every time a user returns to the window. Besides, if the information hasn't been received before, an appropriate state will appear. Easy :)

We do exactly the same with the rest of the necessary methods. Of course, it's impractical to determine this every time you create a VM, so we'll move this logic to basic classes. We'll name them BindingActivity and ActivityViewModel:

public abstract class BindingActivity extends AppCompatActivity {
   …
 
   @Override
   protected void onStart() {
      super.onStart();
      viewModel.onStart();
   }
 
   @Override
   protected void onActivityResult(int requestCode, int resultCode, Intent data) {
      super.onActivityResult(requestCode, resultCode, data);
      viewModel.onActivityResult(requestCode, resultCode, data);
   }
 
   @Override
   protected void onResume() {
      super.onResume();
      viewModel.onResume();
   }
 
   @Override
   public void onBackPressed() {
      if (!viewModel.onBackKeyPress()) {
          super.onBackPressed();
      }
   }
   //….other methods
}
 
public abstract class ActivityViewModel extends BaseObservable {
   …
 
   public void onStart() {
      //Override me!
   }
 
   public void onActivityResult(int requestCode, int resultCode, Intent data) {
      //Override me!
   }
 
   public void onResume() {
      //Override me!
   }
 
   public void onBackPressed() {
      //Override me!
   }
   //….other methods
}

Now, as with the Activity default behavior, we just need to override the appropriate method to react on particular changes.

As for me, there is no need to create bindings and VM connection to it each time you create an activity. This logic also can be moved to the basic class, but with a changing onCreate() method. We'll adapt it for VM when creating activity and add a couple of abstract methods for necessary parameters:

private AppCompatActivity binding;
private ActivityViewModel viewModel;
 
public abstract ActivityViewModel onCreate();
public abstract @IdRes int getVariable();
public abstract @LayoutRes int getLayoutId();
 
@Override
public void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   bind();
}
 
public void bind() {
   binding = DataBindingUtil.setContentView(this, getLayoutId());
   this.viewModel = viewModel == null ? onCreate() : viewModel;
   binding.setVariable(getVariable(), viewModel);
   binding.executePendingBindings();
}

All is left is to make the basic class for ActivityViewModel. Here everything is simpler: just add a copy of the Activity. It is useful to us to create intents and also suitable as a context:

public abstract class ActivityViewModel extends BaseObservable {
 
   protected Activity activity;
 
   public ActivityViewModel(Activity activity) {
       this.activity = activity;
   }
 
   public Activity getActivity() {
       return activity;
   }
   //...lifecycle methods
}

That’s all for activities. We have the necessary tools for describing logic, except for one nasty little things. Such fields as «viewModel» and «binding» in the basic activity are explicitly typed, which makes the work with them more complicated, forcing to get types each time. Therefore let’s summarize our classes as follows:

public abstract class BindingActivity<B extends ViewDataBinding, VM extends ActivityViewModel> extends AppCompatActivity {
 
   private B binding;
   private VM viewModel;
 
   public B getBinding() {
       return binding;
   }
}
 
 
public abstract class ActivityViewModel<A extends AppCompatActivity>
       extends BaseObservable {
 
   protected A activity;
 
   public ActivityViewModel(A activity) {
       this.activity = activity;
   }
 
   public A getActivity() {
       return activity;
   }
}

Done! After all the magic we've got this activity class:

public class ProfileActivity
       extends BindingActivity<ActivityProfileBinding, ProfileViewModel> {
 
   @Override
   public ProfileViewModel onCreate() {
       return new ProfileViewModel(this);
   }
 
   @Override
   public int getVariable() {
       return BR.viewModel;
   }
 
   @Override
   public int getLayoutResources() {
       return R.layout.activity_profile;
   }
 
}

getVariable() should return the name of the variable, which is specified in the tag data->variable of activity xml file, and getLayoutId() should return the same xml. It also worth noting that ProfileViewModel should inherit ActivityViewModel.

The implementation of such classes for fragments is slight differences, but we'll not consider it in details in this article, because concept for all of them is similar. Ready-to-go class can be seen below.

Several useful examples

Since our last article about DataBinding, this library not only has lost its beta status, but also has acquired some very useful innovations. One of them is the two-way binding. Now data affect UI, and vice versa. For example, when a user enters his name in the EditText, the value of variable is also immediately updates. Earlier we've done a similar feature, but it involved TextWatcher and BindingAdapter. Now, this can be achieved much easier. All you need to do is to change
android:text="@{viewModel.text}" на android:text="@={viewModel.text}" (attention to the equality sign after the "@"). That's all :). But such tricks only work with the Observable fields (ObservableInt, ObservableBoolean, ObservableField etc.). That's how a dialogue, where we've changed the status of Mark, looks now:

<layout
   xmlns:android="http://schemas.android.com/apk/res/android">
 
   <data>
       <variable
           name="viewModel"
           type="com.stfalcon.androidmvvmexample.features.dialogs.input.InputDialogVM"/>
   </data>
 
   <EditText
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:hint="@string/dialog_status_text"
       android:text="@={viewModel.text}"
       android:textColor="@color/primary_text"
       android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"/>
 
</layout>

We added a ViewModel here for view in dialogue, as ObservableField transfering directly to the variable does not work correctly (I don't know, is it a bug or a feature, but it is not obvious for sure). Similarly, you can also bind to other attributes, such as checked in CheckBox and RadioButton, enabled and so forth.

If you need to react or change data during its input/output, you can override the get() and/or set() in the Observable field and make the desired manipulation there.

public final ObservableField<String> field = new ObservableField<String>() {
   @Override
   public String get() {
       // TODO: your logic
       return super.get();
   }
 
   @Override
   public void set(String value) {
       // TODO: your logic
       super.set(value);
   }
};

And if the problem is only to track changes, you can add OnPropertyChangedCallback:

field.addOnPropertyChangedCallback(new OnPropertyChangedCallback() {
   @Override
   public void onPropertyChanged(Observable sender, int propertyId) {
      // TODO: your logic
   }
});

Another feature is the ability to use setters as attributes in the markup. Let's suppose we have setAdapter() method at the same RecyclerView. To install it, we need to apply directly to the widget instance and call its methods directly from the code, which is contrary to our approach. To solve this issue, you can create BidningAdapter, or even CustomView, which will expand RecyclerView and you can add your own attributes there. But this is not the best option.
Fortunately, everything is much easier: thanks to code generation we can point setter name in xml while simply omitting the «set». Thus, the adapter can be set like this:

bind:adapter="@{viewModel.adapter}"

The prefix «bind» is the good old «appliaction namespace», and if it has already been announced, it is better to simply duplicate them in order not to confuse the declared custom attributes with the attributes generated by a Binding:

<layout
   xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:bind="http://schemas.android.com/apk/res-auto">
   ...
   <android.support.v7.widget.RecyclerView
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       app:reverseLayout="true"
       bind:adapter="@{viewModel.adapter}"/>
 
</layout>

Nevertheless, the idea of CustomView has the right to life, if there's no desired setter in the widget (or if it's improperly named).

Maybe, someone of you have wondered how to transmit parameters in the ViewModel with such architecture? Approach to delegation is also here, but for convenience we create a static method open (or openForResult), which lists all the necessary parameters. Then we pull out those parameters and pass them in the ViewModel, which has an appropriate constructor. For example, we will give status to our activity as a parameter:

private static final String KEY_STATUS = "STATUS";
 
public static void open(Context context, String status) {
   Intent intent = new Intent(context, ProfileActivity.class);
   intent.putExtra(KEY_STATUS, status);
   context.startActivity(intent);
}
 
@Override
public ProfileActivityVM onCreate() {
   return new ProfileActivityVM(this, getIntent().getStringExtra(KEY_STATUS));
}

Another little feature that I would like to share is the imposition of the «isLoading» and «isError» fields in the basic class ViewModel. These fields are public and are of the ObservabeBoolean type. Because of this there is no need to duplicate the loading state logic and errors. To respond their changes, you can simply use include:

<include
   layout="@layout/part_loading_state"
   bind:visible="@{viewModel.isLoading}"/>

If necessary, you can move the icons and messages for different cases (for example, the different causes for the error) to individual variables, thereby obtaining a flexible component which is implemented with a pair of rows in any layout.

Forget about boilerplate!

While using MVVM, we are faced with the fact that we had to write a lot of annoying code: modification of Activity / Fragment under the basic classes, prescribing long names for Binding-classes in the generics, creating and binding of the ViewModel; and at the early stages we had to copy the basic classes from project to project, which also took precious time. That is why we've created a library and a plug-in for Android Studio, with which this routine began to occupy only 2-3 clicks.

AndroidMvvmHelper library is a set of basic classes for convenient work with MVVM. This list includes classes for working with Activity (BindingActivity and ActivityViewModel), and with Fragment (BindingFragment and FragmentViewModel), which already have binding logic, and the necessary methods for callbacks are also defined. In order to start using it you just need to define dependencies in gradle file:

dependencies {
   ...
   compile 'com.github.stfalcon:androidmvvmhelper:X.X'
}

Although the library solution simplifies developer’s life, the creation of classes is still a quite time-consuming process. To solve this problem, we have developed a plug-in for IntelliJ IDEA and Android Studio — MVVM Generator. It allows one-click creation of BindingActivity class (or BindingFragment), its ViewModel and already prepared marking xml file to register the component in the AndroidManifest (in the case of activity, of course). In addition, if the plugin does not detect dependency of MVVMHelper library, it will be added automatically.

To install it, you need to go to the plugin management section:

MVVM в Android MVVM в Android

Click «Browse repositories» to search for available plugins on the web:

Let's make an MVVM at Android

In the search box, enter «MVVM Generator», select plugin and click «Install»:

Let's make an MVVM at Android

At the end of the installation, you must restart the IDE. After that plugin is ready to use.

Now let’s create a profile fragment. In this case, as when creating a normal class, we’ll call the context menu on the needed package and select «Create Binding Fragment».

Let's make an MVVM at Android

Once we enter the track title (in this case, «ProfileFragment»), we obtain the following:

Let's make an MVVM at Android

When looking inside, we'll see the ready-to-work classes:

public class ProfileFragment
       extends BindingFragment<ProfileFragmentVM, FragmentProfileBinding> {
 
   public ProfileFragment() {
       // Required empty public constructor
   }
 
   public static ProfileFragment getInstance() {
       return new ProfileFragment();
   }
 
   @Override
   protected ProfileFragmentVM onCreateViewModel(FragmentProfileBinding binding) {
       return new ProfileFragmentVM(this);
   }
 
   @Override
   public int getVariable() {
       return BR.viewModel;
   }
 
   @Override
   public int getLayoutId() {
       return R.layout.fragment_profile;
   }
}
 
public class ProfileFragmentVM
       extends FragmentViewModel<ProfileFragment> {
 
   public ProfileFragmentVM(ProfileFragment fragment) {
       super(fragment);
   }
}

In addition to this we have ready-to-go xml:

<layout xmlns:android="http://schemas.android.com/apk/res/android"
       xmlns:app="http://schemas.android.com/apk/res-auto"
       xmlns:tools="http://schemas.android.com/tools">
 
   <data>
 
       <variable
           name="viewModel"
           type="com.stfalcon.androidmvvmexample.features.profile.ProfileFragmentVM"/>
   </data>
 
   <RelativeLayout
       android:layout_width="match_parent"
       android:layout_height="match_parent">
 
   </RelativeLayout>
 
</layout>

And all this in just a few seconds! So...

Summarizing

Plug-in is very simple and solves only the main task — generating the files. There are plans to add check for the presence of bindings, a more flexible title validation, expanded activity templates configuration and much more, but for now — we have what we have.

Since the release of a stable DataBinding version our team has already implemented several projects using this approach. From my own experience I can only say that I do not want to go back to more traditional methods of writing applications, and when it is necessary to do it — you feel yourself like a man from the future. In general, we’ve got less of routine work, and therefore the development process has become more interesting. Besides, the guys from Google are actively working on an adequate support of this technology in Android Studio, which greatly minimizes discomfort while developing a project. Now it’s the basic approach, which is used by us to create applications.

We hope that our experience will make life easier when creating a MVVM-architecture in your application.

Happy Holidays!

P.S. Updated code sample from the previous article can be viewed here.

About author

Android Developer
Sasha develops native apps for Android and loves everything connected with the world of mobile devices. Uses Java, DataBinding, Retrofit 2.0 and Realm in his work. He also creates his own solutions for optimizing the development process.

Related posts

Return to list Return to list