Photo by Christina @ wocintechchat.com on Unsplash

Dialogs in Android MVVM

Vladislav Ermolin (Uladzislau Yarmolin)
ProAndroidDev
Published in
9 min readSep 14, 2020

--

This is a companion article to the presentation on one of the Mobile People community meetups. If you know Russian, you can watch the original presentation here: https://www.youtube.com/watch?v=UOP4OqCaqME

What is this article about?

You will study several approaches to the problem of dialogs’ show up in MVVM architecture, examine their the pros and cons and finally learn how to use Android databinding plugin to display alert dialogs, keeping your Fragment/Activity code clear from the dialog display code.

MVVM keynotes

Lets remind ourselves several important MVVM concepts.

John Gossman

MVVM architecture was revealed to the public by John Gossman in 2005. The original article can be found here.

There are three important statements in that article, which I want you to focus on.

№1

The View is almost always defined declaratively, very often with a tool. By the nature of these tools and declarative languages some view state that MVC encodes in its View classes is not easy to represent.

№2

Model/View/ViewModel also relies on one more thing: a general mechanism for data binding.

№3

The term means “Model of a View”, and can be thought of as abstraction of the view, but it also provides a specialization of the Model that the View can use for data-binding. In this latter role the ViewModel contains data-transformers that convert Model types into View types, and it contains Commands the View can use to interact with the Model.

Taking a closer look

So, in a nutshell MVVM proposes to use so called ViewModel as a bridge between View and Model from the classical MVC pattern. Unlike controller in MVC, ViewModel stores the state of the view and methods, using which View can interact with the Model. On the other side View is bound to the state using databinding and listens for notifications, which come out from the ViewModel. The below diagram explains this concept.

MVVM diagram

In Android the following mapping of the concepts to the classes exist:

MVVM in Android

In Android any class can represent the ViewModel, but Jetpack components provide standard ViewModel implementation, which I’ll use down the article. LiveData represents View’s state and SingleLiveEvent represents the notifications, which ViewModel sends to the View.

SingleLiveEvent implementation can be found in Android architecture samples repository. Even though it is not an official part of the Jetpack components, it is still heavily used by the projects, which implement MVVM pattern. It is useful when you need to send a one-shot event from your ViewModel to the View. Like “open another screen” event.

Databinding in Android can be implemented in a multiple ways.

First of all you can do it manually by implementing LiveData observers in your Fragment/Activity.

Secondary you can use an official data binding library to implement binding in XML.

Problem definition and solution requirements

“I knew all that stuff. Boring. When will the fun part start?” — I could think like this up until now. Please, be patient :) Before the coding starts we need to define the problem, which we want to solve, and the requirements, which we apply to solution.

Let’s start with the problem definition. And for that lets get a closer look at our project’s characteristics:

  1. Our UI had not been using dialogs at all, until we started implementing authentication. This piece of functionality required us frequently solving the problem very similar to the following: login fails, we display user dialog with options to retry/cancel and based on the user’s choice either just hide the dialog or do the retry.
  2. We were actually implementing a platform for the set of applications. Dialog’s UI could vary between the client applications. Some applications could have dialogs replaced with snackbars or error indication baked into the view itself.
  3. In our project we implemented MVVM pattern using the Jetpack components (ViewModel, LiveData, SingleLiveEvent) and Android data binding library. We tried to implement MVVM following the original article. That helped us keeping majority of our Activities/Fragments super thin — they were only containing binding setup. We wanted to keep them thin after adding dialogs to them.

From that definition the following requirements for the solution raised:

  1. Easy to integrate. Integration of dialog must’ve been super easy and require as little amount of code as possible.
  2. Customizable. We must’ve been able to customize the dialogs and easily replace them with other UI patterns.
  3. Match MVVM definition. The most debatable requirement. We’ll refer to it later during the solutions analysis.

Solutions

I’ll tell you about three solutions, which evolved one from another. I’ll describe the pros and cons of every solution and in the end you’ll find a link to the repository with the demo of all three of them.

Each solution review will be presented as the answers to the next three questions:

  1. How to implement the dialog?
  2. How to trigger the appearance of the dialog?
  3. How to handle dialog callbacks in ViewModel?

Enjoy :)

Solution №1 — Dialogs as navigation targets

I think that this solution is the most commonly used by the Android community. The next diagram explains it.

Dialogs as navigation targets

In this solution Dialog acts as navigation target. Whenever ViewModel needs to initiate the dialog, it emits a SingleLiveEvent. The Activity/Fragment subscribes to this event and shows up the dialog, implemented as DialogFragment, whenever the event is received.

DialogFragment handles user actions and passes them back to the ViewModel using EventBus. Alternatively to EventBus we could use interfaces, but that would increase the amount of code needed to integrate dialogs.

Modern readers could suggest using Jetpack navigation or Fragment Result API instead of EventBus. But the first one would also require coding on the Activity/Fragment level while the second one did not exist, when we tackled this problem.

How to implement dialog?

Implement it as DialogFragment. Lets call it CommonDialogFragment.

And navigator for it

How to trigger appearance of the dialog?

Using SingleLiveEvent

Which Activity/Fragment will observe

How to handle dialog callbacks in ViewModel?

Use EventBus to post events in CommonDialogFragment

And then subscribe ViewModel to them

Solution №1 Analysis

Lets analyze the drawbacks of this solution.

The “Duplicated State” problem

In this implementation Dialog is treated as navigtion target. However, for the user it’s just a View. This from MVVM perspective its state must be backed up by ViewModel. But because we use DialogFragment as a recommended way to display dialogs in Android the state is actually backed up by the FragmentManager.

Thus after the process re-creation ViewModel’s state will contain no dialog, while FragmentManager will re-create it.

SavedStateHandle did not exist by the time we worked on this project. But it’s implementation would just make the code harder and would not eliminate the problem itself, but just cure its symptom.

The “Abstraction” problem

From the ViewModel’s perspective Dialog is just another View. And thus it must not differ from the other views in terms of how ViewModel interacts with it. But in the described solution ViewModel is aware of EventBus, which is an implementation detail of the view.

The “Lifecycle” problem

For the solution to be highly re-usable and easy to integrate into any Activity/ Fragment we used EventBus for CommonDialogFragment and ViewModel communication. EventBus has to live in the scope width enough for both CommonDialogFragment and ViewModel i.e. in the singleton scope.

Thus we need to manage lifecycle to prevent potential leaks, while typically View has shorter lifespan than Jetpack ViewModel and thus lifecycle is not a problem.

The “Expandability” problem

Lets imagine that we need to switch our UI implementation from this one

Error as dialog

To this one

Error baked into the view

The non-trivial task, huh? :) Yeah, you’ll have to re-implement it completely. At least because the dialogs are implemented in code, while baked in UI implemented in layout XML.

Summing it all up

  1. “Duplicated State” problem: both ViewModel and FragmentManager control appearance of the CommonDialogFragment;
  2. “Abstraction” problem: ViewModel knows about EventBus which is an implementation detail of CommongDialogFragment. But CommonDialogFragment is just a view for ViewModel;
  3. “Lifecycle” problem: to setup communication between dialog and ViewModel we use EventBus which has to live in singleton scope. We need to manually handle lifecycle;
  4. “Expandability” problem: switching to embed view for error handling requires a lot of modification. Why? It’s just change of the view!
  5. “Expandability” problem: view logic is spread across both XML and fragment/activity :(

Solution №2 — Dialogs as View state

In this solution we’ll do two little changes:

  1. Backup dialog state in ViewModel
  2. Bind dialog to this state
Dialogs as view

How to implement dialog?

We’ll still use CommonDialogFragment and DialogNavigator from the Solution №1. But now lets wrap them into View.

How to trigger appearance of the dialog?

Lets replace SingleLiveEvent with LiveData.

Observe it in ErrorView

How to handle dialog callbacks in ViewModel?

We’ll continue using EventBus, but we’ll extract it from ViewModel.

Finally lets bind our ErrorView to ViewModel. Very similar to how it is done with data binding library.

Solution №2 Analysis

Lets compare this solution with Solution №1.

The “Duplicated State” problem

It is solved! Our ViewModel fully controls the state of the dialog. Even though FragmentManager is still involved it’s synced with ViewModel.

The “Abstraction” problem

It is solved! Thanks to the ErrorView abstraction we could use databinding to connect Dialog view and ViewModel. ViewModel is unaware of EventBus even though we still use it.

Summing it all up

We could solve the “duplicated state” and “abstraction” problems. But our solution still uses EventBus and also requires certain amount of code to exist on the Activity/Fragment level in addition to the databinding code and XML, which makes dialog UI hardly replaceable with baked in error views.

Solution №3 — Use data binding library!

In this solution we’ll utilize the databinding library to display dialogs.

How to implement dialog?

As a custom View!

And a couple of binding adapters

How to trigger appearance of the dialog?

Our ViewModel would look exactly the same as in Solution №2

But instead of implementing data binding manually in code, we’re going to use the power of data binding library.

How to handle dialog callbacks in ViewModel?

We could use exactly the same ViewModel as in Solution №2 again, but our dialogs are so customizable that they can easily use more than two buttons and thus we made them bindable to any data type. The data to bind to is represented by a single field. Because of this callbacks must be passed in as the methods of that field.

So, lets introduce DialogViewModel interface and its implementation inside of our ViewModel will simply delegate all calls to the ViewModel.

And again lets use data binding library to bind dialog to callbacks

Finally lets implement binding in Activity/Fragment. Oh, BTW, we don’t need any code specific to dialogs anymore. Their integration is as simple as integration of any other View into the layout, which is ready for data binding.

Solution №3 Analysis

The “duplicated state” and the “abstraction” problem both remain solved.

The “Lifecycle” problem

We don’t need to handle it anymore.

The “Expandability” problem

Our DialogShowingView can display any layout within the dialog window and bind it to any type. What’s more important is that it is implemented as an Android View, which makes it possible to be configured through XML only. Thus replacing this

Error as dialog

With this

Error baked into the view

Is as simple as replacing one XML with another (see example in the demo).

TL;DR

  1. Dialogs in Android MVVM can be treated as Views;
  2. It is possible to use Android data binding library to display them;
  3. Demo is available here: https://github.com/Lingviston/dialogs-binding-demo

--

--