Build Cross-Platform Reactive .NET Apps
In the previous article, we took a look at several ways of implementing the MVVM pattern on the .NET platform. We found out, that the simplest approach is to use assembly weaving with reactive bindings and extensions. Now we are going to build a sample application using these techniques. Let’s start with the most common task — form validation.
The MVVM (Model-View-ViewModel) architectural pattern enforces the separation between three software layers, so we can think of extracting the layers into different assemblies. In order to achieve this, worth taking a look at .NET Standard — a formal specification of .NET APIs that are available across all .NET implementations.
Using the MVVM architectural approach and .NET Standard code sharing strategy, we can share our ViewModel and Model layers across multiple XAML UI frameworks. If we’ve built an app for the Universal Windows Platform, it would be rather easy to port it to other UI frameworks, such as Xamarin Forms, Avalonia, or Windows Presentation Foundation — UI will be the only thing we’ll need to build from scratch.
Form Validation Logic
Now we are ready to create our first reactive view model and populate it with some logic. Imagine we are developing a complex system and would like to gather feedback from users.
When a user sends us a message, we need to know whether the message is a bug report or a feature suggestion, we also need to group messages by categories. Users shouldn’t be able to submit feedback until they provide all the necessary information. A view model satisfying these conditions might look like the one below.
We mark the properties of our view model class with
Reactive attributes— so all the marked properties will notify our UI when their values change. Using the WhenAnyValue extension method, we can subscribe to such notifications. A call to WhenAnyValue returns an
T represents the type of the observed property value. Then, we apply the
Where operator from reactive extensions that allows us to filter irrelevant events. Inside a lambda passed to the
Subscribe method, we perform another view model property mutation.
WhenAnyValue allows us to observe as many properties as we would like to, so we validate the whole form each time any of the form fields change. The
Log extension method is a magic utility that logs all values emitted by an observable to the debug output. There are logging adapters for NLog, Serilog, and Log4Net. The Publish and RefCount operators are used to ensure the observable event stream will be subscribed only once. We pass the
IObservable<bool> to the ReactiveCommand that is responsible for form submitting, so the command will stay disabled until a user fills the form up.
We mark the view model class with IEnableLogger and IActivatableViewModel interfaces. The former is used to enable logging extensions, and the latter is used to enable the WhenActivated feature. The
DisposeWith extension method attaches the disposable produced by the
Subscribe method call to the
CompositeDisposable that will be disposed of once the view model deactivates.
Testing is an important part of the software development process. We can use XUnit to run unit tests either on .NET Core or on .NET Framework. The NSubstitute library allows us to easily generate stubs and mocks, and FluentAssertions can improve the readability of our tests’ stack traces. Let’s write a test to ensure our presentation logic performs as expected!
UI For Universal Windows Platform
Creating a presentation part of the application is simple: we need to declare controls in XAML, and bind their values to view model’s properties and commands. Here is the code:
We need to implement the
IViewFor<TViewModel> interface in our platform-specific view class. This makes activation and deactivation feature available for both our view and the associated view model. The view model should implement the
IActivatableViewModel interface. A call to
WhenActivated added to the view constructor will activate the corresponding view model.
Finally, we get a good-looking feedback form component!
UI For Xamarin Forms
In order to port the app to Android devices, we create a new Xamarin.Forms project from Visual Studio templates and add a reference to our .NET Standard class library containing view models. XAML markup will look like the one created for the Universal Windows Platform, but we’ll use cross-platform mobile-friendly controls provided by Xamarin.Forms. We also install ReactiveUI.AndroidSupport package into the Xamarin.Android project.
UI For Avalonia & Windows Presentation Foundation
It’s really easy now to port our application to Avalonia and Windows Presentation Foundation frameworks. For each framework, we create a platform-specific project and reference our .NET Standard class library. Also, we install ReactiveUI platform-specific packages according to the ReactiveUI installation guide. From that point, we can start building the UI.
UI For Windows Forms
Another useful feature ReactiveUI provides is type-safe binding that can be used on any platform, even if it isn’t XAML-based. For example, with ReactiveUI.WinForms package we can use the MVVM pattern with Windows Forms. In order to have the bindings working, we create a new form, implement the
IViewFor interface, and place the binding extension methods into the WhenActivated block.
As we see, .NET stack allows us to build truly cross-platform software — using UWP, Xamarin.Forms, WPF, and Avalonia UI XAML frameworks we can bring our applications to devices running Android, iOS, Windows, Linux, and macOS operating systems. The MVVM pattern and such libraries, as ReactiveUI and Fody, can simplify the process of creating portable software, allowing developers to write clear and maintainable code.
The source code of the application described in this article can be found on GitHub: https://github.com/worldbeater/ReactiveMvvm ✨ Additionally, worth taking a look at a cross-platform application that demonstrates the usage of all the features described above. That application can be considered as a more comprehensive example, which is compatible with Windows, Linux, macOS, and Android.
Worth noting that additional validation helpers are available in the new ReactiveUI.Validation NuGet package. ReactiveUI.Validation includes
INotifyDataErrorInfo implementation, helpers that allow to attach and detach validation rules dynamically, and the
BindValidation extension method. The ReactiveUI.Validation library is cross-platform and could be used in both mobile and desktop apps that require complex validations.