Model-View-Presenter (MVP) architecture is widely used in today’s Android apps to separate the view from the presentation logic and the model by introducing a presenter. Model-View-ViewModel (MVVM) is quite similar to MVP, with the view model acting as an enhanced presenter, using a data binder to keep the view model and the view in sync. By binding the view to view model properties, the data binder can handle view updates without the need to manually set changed data back to the view (e.g. no more setText() or setVisibility() on a TextView). As with the presenter in MVP, the view model can easily be unit tested. This article gives an introduction to both the data binding library and the MVVM architectural pattern and how they work together on Android.
Data Binding
What is data binding?
Data binding means that data from data sources is bound to data consumers. For us this usually means that data from local storage or network is bound to layouts. Also, an important feature of data binding is that data changes are automatically synchronized between sources and consumers.
What are the benefits of the data binding library?
TextView textView = (TextView) findViewById(R.id.label); EditText editText = (EditText) findViewById(R.id.userinput); ProgressBar progressBar = (ProgressBar) findViewById(R.id.progress); editText.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void afterTextChanged(Editable s) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { model.setText(s.toString()); } }); textView.setText(model.getLabel()); progressBar.setVisibility(View.GONE);
We’ve all been writing this kind of code. Lots of findViewById() calls and later many calls to setters and listeners and so on. Even with libraries like ButterKnife it doesn’t really get better. With the data binding library, this is a thing of the past.
A binding class is created on compile time for you, which provides all views with an ID as fields. This means no more findViewById(). Actually it’s faster than manually calling findViewById() multiple times, because the data binding library creates code that traverses the view hierarchy only once.
The binding class also implements the binding logic from the layout files, so all those setters are actually called in the binding class. You don’t have to care about it anymore. In short, this means less code in your activities, fragments and view holders.
How to setup data binding?
android { compileSdkVersion 25 buildToolsVersion "25.0.2" ... dataBinding { enabled = true } ... }
First thing to do is to add dataBinding { enabled = true } to your app’s build.gradle. This tells the build system to enable additional processing for data binding, like creating the binding classes from your layout files.
<layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="vm" type="com.example.ui.main.MainViewModel" /> <import type="android.view.View" /> </data> ... </layout>
Next, wrap your layout’s top element in <layout> tags, so that a binding class is created for this layout. The binding class has the name of your layout xml file with Binding added at the end, e.g. ActivityMainBinding for activity_main.xml. As you can see above, namespace declarations also move to the layout tag. Then – inside the layout tag – declare the data you are binding as variables, giving them a name and type. In our case the only variable will be the view model, but more to this later. Optionally you can import classes so that you can use constants like View.VISIBLE or static methods.
How to bind data?
<TextView android:id="@+id/my_layout" android:layout_width="wrap_content" android:layout_height="wrap_content" android:visibility="@{vm.visible ? View.VISIBLE : View.GONE}"> android:padding="@{vm.bigPadding ? @dimen/paddingBig : @dimen/paddingNormal}" android:text='@{vm.text ?? @string/defaultText + "Additional text."}' />
Data binding instructions on view attributes start with an @ and are enclosed by brackets. You can use any variables and imports you declared in the data section. The expressions support nearly everything you can do in code, for example arithmetic operators or string concatenation.
As you can see on the visibility attribute, the ternary if-then-else operator is also supported. A null coalescing operator ?? is provided, which returns the right operand if the left one was null. You can see this above with the text attribute. You can access resources as you would in normal layouts, therefore you can for example choose different dimension resources based on a boolean property of one of your variables, as you can see with the padding attribute.
Properties of your declared variables can be accessed via field access syntax, even if your code uses getters and setters. You can see this again in the text attribute on the slides. vm.text calls the getText() method of the view model. Finally, some small restrictions apply, e.g. no new objects can be created. However, the data binding library is still very powerful.
Which attributes can be bound?
android:text="@{vm.text}" android:visibility="@{vm.visibility}" android:paddingLeft="@{vm.padding}" android:layout_marginBottom="@{vm.margin}" app:adapter="@{vm.adapter}"
Actually, most properties of the standard views are already supported by the data binding library. Internally, the library looks for setters on the view type for the attribute names where you use data binding. For example, when you bind data to the text attribute, the library looks for a setText() method in your view’s class with the right parameter type (in this case String).
This also means that you can use setters, which normally do not have a corresponding layout attribute, by using data binding. For example you could use the app:adapter attribute on a recycler view in the xml layout to set the adapter via data binding.
With standard attributes, not every one of those have a corresponding setter method on the View, for example paddingLeft. In this case, the data binding library ships a custom setter, so that binding to padding attributes works out of the box. Now, what to do when no custom setters are provided by the library, for example for layout_marginBottom?
Custom setters
@BindingAdapter("android:layout_marginBottom") public static void setLayoutMarginBottom(View v, int bottomMargin) { ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) v.getLayoutParams(); if (layoutParams != null) { layoutParams.bottomMargin = bottomMargin; } }
For these cases, custom setters can be written. Setters are annotated with the @BindingAdapter annotation, which takes the layout attribute name as argument, for which the binding adapter should be called. Above you see a binding adapter for layout_marginBottom.
The method must be public static void and must accept as parameters first the view type for which the binding adapter should be called and then the data to be bound with your desired type. In this example, we define a binding adapter for type View (and subtypes) with a bound type of int. Finally, implement the binding adapter. For layout_marginBottom, we need to get the layout parameters and set the bottom margin. Easy.
@BindingAdapter({"imageUrl", "placeholder"}) public static void setImageFromUrl(ImageView v, String url, int drawableId) { Picasso.with(v.getContext().getApplicationContext()) .load(url) .placeholder(drawableId) .into(v); }
It’s also possible to require multiple attributes to be set for a binding adapter to be called. To achieve this, provide your list of required attribute names to the @BindingAdapter annotation. Also, each of these attributes now have a typed parameter in the method. These BindingAdapters are only called, when all declared attributes are set.
I always like to define a binding adapter for loading images from URLs with a placeholder during loading. As you can see above, this is pretty easy with binding adapters and by using the Picasso image loading library. However, you can use any approach you want in your custom binding adapters.
Applying the binding in code
MyBinding binding; // For Activity binding = DataBindingUtil.setContentView(this, R.layout.layout); // For Fragment binding = DataBindingUtil.inflate(inflater, R.layout.layout, container, false); // For ViewHolder binding = DataBindingUtil.bind(view); // Access the View with ID text_view binding.textView.setText(R.string.sometext); // Setting declared variables binding.set<VariableName>(variable);
Now that we defined our bindings in the xml file and have written custom setters, how do we apply the binding in code? The data binding library does all the hard work for us, by generating a binding class. For getting an instance of the corresponding binding class for your layout, use the helper methods provided by the library. For activities use DataBindingUtil.setContentView(), for fragments use inflate() and for view holders use bind(). As already mentioned, the binding class provides all views which have an ID defined as final fields. Also, you set the variables you declared in the layout files on the binding object.
Auto-updating the layout
One of the benefits of data binding is that the layout can be updated automatically by the library when data changes. However, the library still needs to be notified of data changes. This is accomplished by having the variables you set on the binding implement the Observable interface (don’t confuse this with the RxJava Observable).
For simple data types like int or boolean, the library already provides appropriate types which implement Observable, for example ObservableBoolean. Also, there is an ObservableField type for use with other objects, like strings.
public class MyViewModel extends BaseObservable { private Model model = new Model(); public void setModel(Model model) { this.model = model; notifyChange(); } public void setAmount(int amount) { model.setAmount(amount); notifyPropertyChanged(BR.amount); } @Bindable public String getText() { return model.getText(); } @Bindable public String getAmount() { return Integer.toString(model.getAmount()); } }
In more complex situations, like with view models, a BaseObservable class exists, which provides easy methods for notifying the layout of changes. As you can see above in the setModel() method, we can then update the whole layout at once when the model changes by calling notifyChange().
When you look at setAmount(), you see that only one property of our model is changed. In this case, we do not want the whole layout to be updated, but just those parts that use this exact property. To achieve this, the corresponding getters of the property can be annotated with @Bindable. Then, a field in the BR class is generated, which can be passed into the notifyPropertyChanged() method. With this, the data binding library only updates those parts of your layout that actually depend on the changed property.
Summary
- Declare variables in the layout files and use them to bind attributes of your views.
- Create the binding in code and set the variables.
- Make sure that your variable types implement Observable – for example by extending BaseObservable – so that data changes are automatically reflected by the layouts.
Model View ViewModel architecture
Now let’s look at the ModelViewViewModel architecture and how the three components in this architectural pattern work together.
- 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 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, generelly 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 an = 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 straight forward 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.
Lifecycle and State
Another thing to consider when implementing ModelViewViewModel architecture in your apps is how you handle lifecycle and state. First, I suggest you create a base class for your view models where you can handle this kind of stuff.
public abstract class BaseViewModel<V extends MvvmView> extends BaseObservable { private V view; @CallSuper public void attachView(V view, Bundle sis) { this.view = view; if(sis != null) { onRestoreInstanceState(sis); } } @CallSuper public void detachView() { this.view = null; } protected void onRestoreInstanceState(Bundle sis) { } protected void onSaveInstanceState(Bundle outState) { } protected final V view() { return view; } }
As you know, the lifecycle callbacks happen in Activities and Fragments. But now, we do all the stuff in our view models. Therefore we need to forward lifecycle callbacks. I suggest using only two callbacks, since these should be sufficient for most needs: attachView(), which signals that the view is created and detachView(), which signals that the view is destroyed. In attachView(), you also pass in the view interface, which can be used to send commands to the view, if ever needed. attachView() is usually called in onCreate() or onCreateView() for fragments and detachView() is then called in onDestroy() and onDestroyView().
Now, activities and fragments also provide callbacks for saving state when the system kills your components or a configuration change is happening. As our state is now stored in the view model, we need to relay these callbacks to the view model. I suggest passing the savedInstanceState directly to attachView(), so that restoring can automatically take place here. For saving the state, a separate onSaveInstanceState() method in the view model is needed, which must be called on the corresponding callback in activities and fragments. In general, I recommend to create a separate state class for each view model, if you have UI state. When this class implements Parcelable, saving and restoring state is easy, because you only have one object to save and restore.
Views
public abstract class BaseActivity<B extends ViewDataBinding, V extends MvvmViewModel> extends AppCompatActivity implements MvvmView { protected B binding; @Inject protected V viewModel; protected final void setAndBindContentView(@LayoutRes int layoutResId, @Nullable Bundle sis) { binding = DataBindingUtil.setContentView(this, layoutResId); binding.setVariable(BR.vm, viewModel); viewModel.attachView((MvvmView) this, sis); } @Override @CallSuper protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); if(viewModel != null) { viewModel.onSaveInstanceState(outState); } } @Override @CallSuper protected void onDestroy() { super.onDestroy(); if(viewModel != null) { viewModel.detachView(); } binding = null; viewModel = null; } }
Now, lets talk about the views. Here, I too recommend to create base classes. The example above is a base class for activities. View models should be available to the base classes (for example by injecting them), so that we can have a single method to initialise our architecture setup. Then you just have to call this method in an activity’s onCreate() or a fragment’s onCreateView().
Above, this is handled by the setAndBindContentView() method, which can then be called in onCreate() instead of the usual setContentView(). This method sets the content view and creates the binding. Then it sets the view model variable on the binding and attaches the view to the view model, also providing the saved instance state.
As you can see, the onSaveInstanceState() and detachView() callbacks can also be handled in the base classes. onSaveInstanceState() just forwards the callback to the view model, while onDestroy() calls detachView() on the view model.
With the base classes set up, you now can start with implementing MVVM architecture in your apps.
More things to consider
While you have a decent base for Android apps with MVVM architecture now, there are a few things you can do to improve your app architecture even further.
Dependency Injection
I recommend to use a dependency injection framework like Dagger 2 to inject components. This can and should be used quite extensively. With dependency injection you can inject other components very easily into the view model. And the view model does not care where the components come from. Dependency injection acts like the glue that connects your components.
This cleans up your code and increases testability by decoupling your code further. Also, this is great for maintenance, as it enables you to change the injected components at any time. What’s important here is to rely on interfaces when you want true exchangeability.
Business Logic
Remember: View models should only contain presentation logic, so don’t put business logic in your view models. Create repository interfaces for your model classes and implement them with a storage option of your choice.
public interface ModelRepo { Single<List<Model>> findAll(); Single<Model> findById(int id); void save(Model model); void delete(Model model); }
Or, for networking, use Retrofit, which creates the networking code for an interface you define.
public interface ModelRepo { @GET("model") Single<List<Model>> findAll(); @GET("model/{id}") Single<Model> findById(@Path("id") int id); @PUT("model") Completable create(@Body Model model); }
If you just need basic operations like find, create and so on, you are done here and can inject the repositories directly into the view models to fetch and manipulate data. For more complex use cases, like validation and so on, create separate components that implement this behaviour and inject these into the view model.
Navigation
Another topic which is especially relevant on Android is navigation, since you need components provided by the view, be it the Context for starting an activity or be it a FragmentManager for replacing fragments. While you could just call navigation commands via the view interface, this brings additional complexity into your architecture.
To solve this, a separate component can be used, which handles navigation in your app. The Navigator interface defines common methods for starting activities, handling fragments and so on. Then, you can create specific implementations for activities and fragments and inject these into your view models. This means you can do navigation directly in your view models, without ever needing a context or a fragment manager, as these are handled by the navigator implementations.
public interface Navigator { String EXTRA_ARGS = "_args"; void finishActivity(); void startActivity(Intent intent); void startActivity(String action); void startActivity(String action, Uri uri); void startActivity(Class<? extends Activity> activityClass); void startActivity(Class<? extends Activity> activityClass, Bundle args); void replaceFragment(int containerId, Fragment fragment, Bundle args); void replaceFragmentAndAddToBackStack(int containerId, @NonNull Fragment fragment, Bundle args, String backstackTag); ... }
This is also convenient for view holders, where you can use the navigator in the view holder’s view model to navigate, e.g. to start a new activity when a card in your recycler view is clicked.
Unit testing
Finally, lets look at unit testing view models. As I already mentioned, testing your presentation logic becomes quite easy with MVVM architecture. I prefer using Mockito, so I can mock the view interface and other components which are injected into the view model. For more demanding tests, you can also use PowerMock, which uses bytecode manipulation, e.g. to allow mocking static methods.
public class MyViewModelUnitTest { @Mock ModelRepo modelRepo; @Mock Navigator navigator; @Mock MvvmView myView; MyViewModel myViewModel; @Before public void setup() { MockitoAnnotations.initMocks(this); myViewModel = new MyViewModel(modelRepo, navigator); myViewModel.attachView(myView, null); } @Test public void buttonClick_submitsForm() { final Model model = new Model(); doReturn(model).when(modelRepo).create(); myViewModel.onButtonClick(null); verify(modelRepo).save(model); verify(navigator).finishActivity(); } }
In the setup() method, initialize the mocks, create the view model, at the same time injecting mock objects and finally attach the view interface to the view model. The rest is quite simple. Write test cases by first specifying the behaviour of your mock objects with the doReturn().when() syntax of Mockito, if needed. Then you call the method under test in the view model. And finally you use assertions and verify() methods to check whether the return value is correct or methods in your mock objects were called the way they should.
Summary
This was an overview, of how you can structure your app’s architecture with the data binding library, following the ModelViewViewModel pattern. To summarize it:
- The view model is the middleman between the view and the model.
- The view is automatically updated with properties of the view model via data binding.
- Commands in the view model are called on view events.
- The view model can also call commands on the view, if necessary.
- In Android, view models can handle basic lifecycle callbacks and state saving and restoring.
- Use dependency injection, which helps with testing and gets you cleaner code.
- Don’t put business logic in view models. They should only contain presentation logic. Use repositories for data access.
- Use a navigator component for navigation in Android apps.
If you want to take a closer look at an example app using these concepts, you can do so with my Countries app on GitHub. There is also a template branch, which can be used to start a new app, following these guidelines. Both the example app and the template are also available in Kotlin.
Finally, the approach outlined in this article is one way of doing MVVM architecture. There is not a single right way to do this, many approaches are possible. I’d love to hear about your approach and thoughts about improvements of mine in the comments section.
Hey,
thanks for your article.
I have a small amendment. With the announcement of the new LiveData things and ViewModel of the android architecture team, it is stated:
“Note: Since the ViewModel outlives specific activity and fragment instantiations, it should never reference a View, or any class that may hold a reference to the activity context. If the ViewModel needs the Application context (for example, to find a system service), it can extend the AndroidViewModel class and have a constructor that receives the Application in the constructor (since Application class extends Context).”
So in this case, in the code above BaseViewModel should not keep “private V view;”.
But of course, your code is also right.
Thanks again
Sascha
hi patrick,
thanks for this article, which helped me a lot to get my head arround the mvvm pattern.
i have one question though (i’m relating to your countries sample project for better understanding): imagine you have to implement another fragment which again shows a list of countries. but this time you have to perform another action on click (instead of opening the details activity).
how would you achieve this? since the viewmodel gets injected into the viewholder and the viewholder gets created by the adapter would you have to implement a new adapter plus viewholder plus viewmodel?
there must be a more elegant way, of which i cant think of right now. what would be your way of achieving this behaviour?
thanks a lot!