Contents

MVVM (Architecture)

Remarks

Syntax quirks with DataBinding

When binding a viewModel function to a property in xml certain function prefixes like get or is are dropped. Eg. ViewModel::getFormattedText on the ViewModel will become @{viewModel.formattedText} when binding it to a property in xml. Similarly with ViewModel::isContentVisible -> @{viewModel.contentVisible} (Java Bean notation)

The generated binding classes like ActivityMainBinding are named after the xml they're creating bindings for, not the java class.

Custom Bindings

In the activity_main.xml I set the textColor attribute on the app and not the android namespace. Why is that? Because there is a custom setter defined for the attribute textColor that resolves a ColorRes resource id send out by the ViewModel to an actual color.

You could argue that the things I do in xml for android:visibility and app:textColor are wrong/anti-patterns in the MVVM context because there is view logic in my view.
However I would argue it is more important for me to keep android dependencies out of my ViewModel for testing reasons.

Besides, what really does app:textColor do? It only resolves a ressource pointer to the actual color associated with it. So the ViewModel still decides which color is shown based on some condition.

As for the android:visibility I feel because of how the method is named it is actually okay to use the ternary operator here. Because of the name isLoadingVisible and isContentVisible there is really no doubt about what each outcome should resolve to in the view. So I feel it is rather executing a command given by the ViewModel than really doing view logic.

On the other hand I would agree that using viewModel.isLoading ? View.VISIBLE : View.GONE would be a bad thing to do because you are making assumptions in the view what that state means for the view.

Useful Material

The following resources have helped me a lot in trying to understand this concept:

MVVM Example using DataBinding Library

The whole point of MVVM is to separate layers containing logic from the view layer.

On Android we can use the DataBinding Library to help us with this and make most of our logic Unit-testable without worrying about Android dependencies.

In this example I'll show the central components for a stupid simple App that does the following:

At start up fake a network call and show a loading spinner

Show a view with a click counter TextView, a message TextView, and a button to increment the counter

On button click update counter and update counter color and message text if counter reaches some number

Let's start with the view layer:

activity_main.xml:

If you're unfamiliar with how DataBinding works you should probably take 10 minutes to make yourself familiar with it.
As you can see, all fields you would usually update with setters are bound to functions on the viewModel variable.

If you've got a question about the android:visibility or app:textColor properties check the 'Remarks' section.

This will trigger changes on the model and format data from the model to show them on the view.
Note that it is here where we evaluate which GUI representation is appropriate for the state given by the model (resolveCounterColor and resolveLabelText). So we could for example easily implement an UnderachieverClickerModel that has lower thresholds for the state of excitement without touching any code in the viewModel or view.

Also note that the ViewModel does not hold any references to view objects. All properties are bound via the @Bindable annotations and updated when either notifyChange() (signals all properties need to be updated) or notifyPropertyChanged(BR.propertyName) (signals this properties need to be updated).

Here we see the view initializing the viewModel with all dependencies it might need, that have to be instantiated from an android context.

After the viewModel is initialized it is bound to the xml layout via the DataBindingUtil (Please check 'Syntax' section for naming of generated classes).

Note subscriptions are subscribed to on this layer because we have to handle unsubscribing them when the activity is paused or destroyed to avoid memory leaks and NPEs. Also persisting and reloading of the viewState on OrientationChanges is triggered here