Zen Android MVVM

After multiple variations and iterations of implementing the MVP pattern in various Android applications, I decided to explore implementing MVVM using the Android Data Binding library as the basis. The results have me in a state of near Android-coding-bliss.

Before walking you through the steps that I took to approach this nirvana, I want to share the goals that I had for this endeavor:

  • A MVVM unit should consist of nothing more of a ViewModel (VM), it’s state (M) and a bound layout resource file (V).
  • Each MVVM unit should be modular and nestable. A MVVM unit should be able to contain one or more child units, which in turn may contain their own children.
  • No extension of a base Activity, Fragment, or custom View should be required.
  • A base ViewModel class is both acceptable and expected, but it must expose no Android-specific dependencies. It should be testable using vanilla JUnit.
  • All ViewModel dependencies should be injected.
  • One- and two-way data bindings to ViewModel properties and methods should be done declaratively from the layout file.
  • A ViewModel must have no knowledge of the View it supports. There should be no imports of anything from theandroid.view or android.widgetpackages.
  • A ViewModel should be automatically be bound to the attach/detach lifecycle of its paired View.
  • A ViewModel should be independent of the Activity lifecycle but should have access to it as needed.
  • The pattern must work regardless of whether a single- or multi-Activity approach is taken.

First things first…

Right out of the gate, I select a few tools that take care of some low-hanging (but delicious) fruits: Toothpick for dependency injection, and my own Okuki library for navigation and back-stack management. I assume others may instead use Dagger for DI. For navigation you may prefer Intents, EventBus, or some other custom navigation-management mechanism. Also you may prefer to use Activities and Fragments for back-stack management.* It’s neither here nor there to me. I only recommend that you address these problems on their own, in a centralized and decoupled way, regardless of whether you chose MVP, MVVM, or any other UI architecture.

* A suggested way that to use a FragmentManager for its back-stack is included towards the end of this article.

Base ViewModel and Lifecycle

Having DI, navigation, and back-stack taken care of, I next set about defining a base ViewModel class and a mechanism for tying it to the View attach/detach lifecycle.

First I define an interface for ViewModel:

public interface ViewModel {
void onAttach();
void onDetach();
}

Next I make use of the bindings for View.OnAttachStateListener that are provided by the Data Binding Library and map the android:onViewAttachedToWindow and android:onViewDetachedFromWindow attributes to corresponding methods in my base ViewModel. I finalize these methods, and wire them to the ViewModel interface’s onAttach and onDetach methods, thus hiding the required View parameters from extending classes. Additionally, I integrate dependency injection and an auto-dispose mechanism for Rx subscriptions, and I wire these into the View lifecycle as well.

My resulting BaseViewModel class looks like this:

public abstract class BaseViewModel implements ViewModel {

private final CompositeDisposable compositeDisposable = new CompositeDisposable();
    public BaseViewModel() {
App.inject(this);
}
    @Override
public void onAttach() {
}

@Override
public void onDetach() {
}

public final void onViewAttachedToWindow(View view) {
onAttach();
}

public final void onViewDetachedFromWindow(View view) {
compositeDisposable.clear();
onDetach();
}

protected void addToAutoDispose(Disposable... disposables) {
compositeDisposable.addAll(disposables);
}

}

Now to make use of any ViewModel extended from this base, it is simply a matter of binding the ViewModel to the layout, and mapping the attach/detach attributes to the root ViewGroup like this:

<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="vm" type="MyViewModel"/>
</data>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:onViewAttachedToWindow="@{vm::onViewAttachedToWindow}"
android:onViewDetachedFromWindow="@{vm::onViewDetachedFromWindow}"

>
</FrameLayout>
</layout>

Modular Units

Now that I have a way to bind a ViewModel to a View and its lifecycle, I next need a way to load a MVVM unit into a container in a consistent, modular way. To do this I first define an interface that provides the mapping between a ViewModel and a layout resource:

public interface MvvmComponent {
int getLayoutResId();
ViewModel getViewModel();
}

Then I define a custom data binding for MvvmComponent which performs the inflation of the provided layout, binds it with the ViewModel, and loads it into a ViewGroup:

@BindingAdapter("component")
public static void loadComponent(ViewGroup viewGroup, MvvmComponent component) {
ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater.from(viewGroup.getContext()), component.getLayoutResId(), viewGroup, false);
View view = binding.getRoot();
binding.setVariable(BR.vm, component.getViewModel());
binding.executePendingBindings();
viewGroup.removeAllViews();
viewGroup.addView(view);
}

Note that I set the attachToParent parameter to false when I do the inflation, and perform addView(view) explicitly after the ViewModel is bound. The reason for this is that I need the ViewModel to be bound before the inflated View is attached so that the ViewModel's onViewAttachedToWindow method is properly invoked.

Now I can make use of this new binding. In my layout I define a container ViewGroup by adding the new component attribute:

<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="vm"
type="MyViewModel"/>
</data>
<FrameLayout
android:id="@+id/main_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:onViewAttachedToWindow="@{vm::onViewAttachedToWindow}"
android:onViewDetachedFromWindow="@{vm::onViewDetachedFromWindow}"
app:component="@{vm.myComponent}"
/>
</layout>

In my bound ViewModel I make use of anObservableField<MvvmComponent> to provide a way of switching out the component:

public class MyViewModel extends BaseViewModel {
  public final ObservableField<MvvmComponent> myComponent 
= new ObservableField<>();

@Override
public void onAttach() {
myComponent.set(new HelloWorldComponent("World"));
}
}

The component class itself abstracts the layout resource ID and child ViewModel definitions from the calling parent ViewModel, receiving from the parent ViewModel only the data that is required to initialize the child ViewModel:

public class HelloWorldComponent implements MvvmComponent {
private final String name;

public HelloWorldComponent(String name){
this.name = name;
}
  @Override
public int getLayoutResId() {
return R.layout.hello_world;
}
  @Override
public ViewModel getViewModel() {
return new HelloWorldViewModel(name);
}
}

Now child components can be easily loaded based on a ViewModel state without a ViewModel knowing anything about layouts, Views, or other ViewModels.

Activity Lifecycle

As originally intended, my MVVM units are independent of the Activity lifecycle. But there are times when we may need access to it. We might need to save and restore data using the instance-state Bundle, or we may need to respond to pause/resume events. As these are needed, however, they are easily provided. To do so, simply delegate these events to a singleton that implements Application.ActivityLifecycleCallbacks and is registered with Application. The singleton can then expose the events via Listeners or Observables and be injected to whatever ViewModel needs access to them.

Using Fragments for back-stack

As I mentioned towards the beginning of this post, I use a custom library for back-stack management. However, with a few slight adjustments to the code above, you can leverage Android’s FragmentManager instead. To do this, a couple additional methods to the MvvmComponent interface:

public interface MvvmComponent {
int getLayoutResId();
ViewModel getViewModel();
String getTag();
boolean addToBackStack();
}

Next, create a Fragment to wrap your MVVM unit like this:

public class MvvmFragment extends Fragment {
  private int layoutResId;
private ViewModel vm;
public MvvmFragment newInstance(int layoutResId, ViewModel vm){
MvvmFragment fragment = new MvvmFragment();
fragment.layoutResId = layoutResId;
fragment.vm = vm;
return fragment;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
ViewDataBinding binding = DataBindingUtil.inflate(inflater, layoutResId, container, false);
binding.setVariable(BR.vm, vm);
binding.setVariable(BR.fm, getChildFragmentManager());
return binding.getRoot();
}
  public void setLayoutResId(int layoutResId){
this.layoutResId = layoutResId;
}
  public void setViewModel(ViewModel vm){
this.vm = vm;
}
}

Note that you’re layouts will need to have fm data variables declared and set as attributes on the container ViewGroups. Also, be aware of the implications of configuration changes and process death for your layoutResId and vm members on your MvvmFragment, and make proper us of your Fragment arguments.

Now you can modify the custom component binding to make use of your MvvmFragment instead of performing the inflation and ViewModel binding directly:

@BindingAdapter({"component", "fm"})
public static void loadComponent(ViewGroup viewGroup, MvvmComponent component, FragmentManager fm) {
MvvmFragment fragment = fm.findFragmentByTag(component.getTag());
if(fragment == null) {
fragment = MvvmFragment.newInstance(component.getLayoutResId, component.getViewModel());
}
FragmentTransaction ft = beginTransaction();
ft.replace(viewGroup.getId, fragment, component.getTag());
if(component.addToBackStack()){
ft.addToBackStack(component.getTag());
}
ft.commit();
}

Example Application

For a fully functional application example that uses this MVVM approach (without the Fragments) see my sample here.

Happy coding!