iTranslated by AI
XAML Architecture: Rethinking the Necessity of Dedicated ViewModel Classes
This was intended to be the article for the 22nd day of the Xamarin Advent Calendar 2018... but I miscalculated the day and missed it by an hour and a half. To those who filled in for me, thank you very much. I am sorry.
Well, the content is a bit broader, touching on UI frameworks that utilize XAML in general, such as WPF and UWP.
I have long believed that in XAML architectures, it is not strictly necessary to separate the (top-level) ViewModel class from the View class. Could we not implement the XAML code-behind as the ViewModel? While the ViewModel is necessary as a role, is it necessarily required as a class? That has been my line of thinking.
Of course, this does not mean that a ViewModel class is never needed. Certain constraints must be met, and it requires enough organizational control to enforce those constraints strictly.*1
In this article, I would like to organize the rationale for this idea.
What is the benefit?
Because the View and ViewModel are separated, everyone has likely experienced frustrations such as the following:
- I want to notify the ViewModel of events like a ListView's SelectedItem, but I cannot set a Command directly.
- I want to open an alert or confirmation dialog, but how do I open it from the ViewModel and receive the result?
If the ViewModel were written in the XAML code-behind, you wouldn't have to worry about these. Although, for the latter, there is the design question of whether it is appropriate to display dialogs from a ViewModel.
How to implement without a ViewModel class?
First, as stated in the title, discarding the ViewModel class does not mean eliminating the role of the ViewModel.
Instead of separating the ViewModel into an independent class, we use the XAML code-behind as the ViewModel. Of course, we still use data binding, and the roles of View and ViewModel remain separated. The View is described in .xaml, and the ViewModel is described in .xaml.cs, and the roles (Roles) themselves are designed and implemented separately.
Since you are all, of course, writing nothing but the InitializeComponent call in your code-behind (just kidding, I assume there isn't much written there), you should be able to write the ViewModel code there without it mixing with the View implementation. In fact, since it is a partial class, you could even prepare a separate file for the ViewModel in the same class.
Let's show a simple concrete example. First, the code-behind:
public partial class MainPage : ContentPage
{
public string Message { get; } = "Hello, World without ViewModel!";
public MainPage()
{
InitializeComponent();
}
}
The Message property is defined in the code-behind. Let's bind this to the View.
Next is the code for MainPage.xaml:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XFApp.MainPage"
x:Name="This"
BindingContext="{Binding Source={x:Reference This}}">
<Label Text="{Binding Message}"
HorizontalOptions="Center"
VerticalOptions="Center" />
</ContentPage>
There are two points here:
- The ContentPage instance is named "This".
- We use x:Reference to set a reference to itself in the BindingContext.*2
Since the instance of the MainPage class itself is set to the BindingContext, the same implementation as when the ViewModel class is separated is possible.
What are the necessary constraints?
Specifically, they are as follows:
- Do not perform Unit Tests on the ViewModel.
- Describe the View role only in .xaml and the ViewModel role only in .xaml.cs, and do not cross boundaries inadvertently.
In my notes, it says that 3 constraints are needed, but I could never recall what the third one was. Well, that's fine (it's not). Two is probably correct.
Thoroughly enforcing the former should not be difficult. However, the latter can easily be violated if you are not careful.
For this, the team needs strong control to enforce these constraints. If you think you cannot enforce it, it is better to simply separate the ViewModel class.
However, even if you exercise control, if you imply that you should never cross the boundary conditions when View and ViewModel are integrated, then there is no merit to writing the ViewModel in the View's code-behind. There would be no point in discarding and integrating the ViewModel class.
Within the range determined by the team, you need control to allow crossing the normal boundaries of View and ViewModel, while ensuring you do not exceed the range you decided not to cross. If you can do that, you should be able to enjoy the benefits mentioned above.
Aside: If you do not integrate View and ViewModel classes
By the way, to those who read this far and thought, "I don't need to read this because I'm not going to integrate them," are you including the View and ViewModel classes in the same assembly?
If you choose the option of separating the View and ViewModel classes to divide their roles, you should also separate the assemblies. Why? Because if they are in the same assembly, it is easy to cross boundaries.
Look at the following code:
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
BindingContext = new MainPageViewModel { MainPage = this };
}
}
public class MainPageViewModel
{
public MainPage MainPage { get; set; }
public void Action()
{
MainPage.Navigation.PushAsync(new SecondPage());
}
}
This is careless code, but it's surprisingly tempting to write. I have done it in the past, and I occasionally see this example on the web as a way to resolve notification from ViewModel to View. Mostly in older articles where the understanding of MVVM was not advanced.
Objects necessary for alert display and screen navigation are on the View side.
In the example above, to navigate screens from the ViewModel, it receives the View to which that ViewModel belongs as a property and operates it. If you do this, the meaning of separating the View and ViewModel is lost.
There are two ways to prevent this:
- Avoid it through control/management.
- Separate the assemblies for View and ViewModel.
By establishing references only from the View to the ViewModel, this implementation can be perfectly avoided. Since there is no reason not to separate the assemblies, you should separate the View and ViewModel assemblies.
Now, let's delve a little deeper.
Is it correct not to perform Unit Tests on the ViewModel?
For me, "Yes".
I am a self-confessed testing junkie, but recently I am skeptical about quality assurance solely on the ViewModel. I would do it if I had infinite time and money.
View and ViewModel are loosely coupled by design, but in reality, they cannot be implemented without being aware of each other. View and ViewModel work together to realize the Presentation, so that's inevitable. They are merely implementing different domains for the same concern.
Therefore, when a change request occurs for the Presentation, unless it is a trivial issue, it is highly likely that both the View and the ViewModel will be affected.
In such a case, how much value is there in the fact that the ViewModel operates independently?
Testing costs money. And it's not "build and done"; it needs to be maintained. Even if you pay the high cost of writing ViewModel unit test code, only a portion of the Presentation quality is guaranteed.
The value of Presentation is only realized when it is integrated with the View. I believe that the option of investing the ViewModel unit test cost elsewhere is a realistic one. For example, working on automatic testing of the Presentation.
Although, if the exact same ViewModel is actually deployed to different Views, there might be value in automatic testing the ViewModel alone.
Why don't you write code in the code-behind in the first place?
Even though it's called code-behind. Although I don't necessarily think it's always bad.
When implemented using a XAML architecture, it seems common to push View implementation to XAML as much as possible. And I often see examples where nothing is implemented in the code-behind other than InitializeComponent.
This is not because it is "cooler", but there is a clear reason. I believe the reasons are mainly the following two. Please let me know if there are others.
- Code written in the code-behind cannot be reused (should not be).
- Code written in the code-behind cannot be tested.
- Manually generating child UI elements of a ListView etc. often leads to trouble.
It is not that it is impossible, but calling code written in a top-level View from another View is not how it should be. If you want to do that, you should extract a common class using Behaviors or Triggers and move the implementation there.
And testing code written in the code-behind, while not impossible, is difficult. This is mainly because UI elements are expected to operate only on the UI thread, so they have low compatibility with unit test frameworks.*3
Also, for example, child UI elements of a ListView are sometimes assumed to be virtualized rather than generated for all elements. That's the Flyweight pattern. In this case, trying to control them manually is likely to cause bugs. To avoid this, the right path is to correctly use the mechanisms of binding and DataTemplate.
As a result, there is not much code left in the code-behind, so in well-written sample code, it often happens that no implementation is included in the code-behind.
Conversely, when it is clear that it will not be reused and is not worth testing individually,*4 there should be no problem writing it in the code-behind.*5
Inconveniences of including the ViewModel in the code-behind
Common MVVM frameworks may become difficult to use.
This is because MVVM frameworks that assume the adoption of the Dependency Injection (DI) pattern often expect the ViewModel class to be the root object of DI.
Of course, you could treat the top-level object of the Model or the View as the root object of DI, but there is no guarantee that the framework will work correctly for screen transitions, etc.
If you adopt an uncommon implementation method, you may suffer inconveniences from this direction.
As assets accumulate, the merit of integrating View and ViewModel classes diminishes
In the first place, the motives for wanting to integrate the View class and the ViewModel class were as follows:
- I want to notify the ViewModel of events like a ListView's SelectedItem, but I cannot set a Command directly.
- I want to open an alert or confirmation dialog, but how do I open it from the ViewModel and receive the result?
These points that I think are a bit inconvenient will be felt less as assets for implementing the MVVM pattern accumulate, and the motivation to integrate will gradually fade.
Specifically for Xamarin.Forms, a wonderful library called Xamarin.Forms.BehaviorsPack that resolves many frustrations has been released by me (ahem. I just noticed that it's not written in the documentation, but it also provides a mechanism to bind Commands to all events of standard Xamarin.Forms UI objects).
That said, it's not like the times you feel inconvenienced will disappear completely. But that might be a necessary cost.
Summary
If you can accept the following two constraints, isn't the option of eliminating the ViewModel class and integrating it with the View, implementing the View in XAML and the ViewModel in the code-behind, one of the realistic options?
- Do not perform Unit Tests on the ViewModel.
- Describe the View role only in .xaml and the ViewModel role only in .xaml.cs, and do not cross boundaries inadvertently.
However, the latter requires the organizational control to enforce it strictly. And I think that is a very difficult problem.
By integrating the ViewModel class into the View class, it becomes easier to solve problems that feel inconvenient, such as:
- I want to notify the ViewModel of events like a ListView's SelectedItem, but I cannot set a Command directly.
- I want to open an alert or confirmation dialog, but how do I open it from the ViewModel and receive the result?
Nevertheless, I think these are problems that can generally be solved once you have implemented MVVM and accumulated assets. However, it is inconvenient in the beginning, and there is no guarantee that everything you need to solve them is available in the world.
Conversely, if you choose to separate the View and ViewModel classes, you should separate their assemblies (i.e., projects). If you do not have sufficient control, you may end up crossing the roles of View and ViewModel and falling into a state of mutual reference.
Ultimately, I think the background that leads me to question keeping View and ViewModel classes separated is that I don't find value in ViewModel unit tests, and even if you take an architecture where View and ViewModel are loosely coupled, you logically have to understand both anyway.
How will you choose? It might be worth reconsidering as a thought game.
That's all.
Discussion