This is a guest blog from Adolfo Marinucci.
I’m a full-stack developer from Italy, primarily focused on Microsoft technologies ranging from cross-platform applications with .NET MAUI to ASP.NET Core backend services hosted on Azure. I’m the creator of MauiReactor, a UI library for .NET MAUI that brings the Model-View-Update (MVU) pattern to cross-platform development.
Background and Motivation
MauiReactor is an open-source .NET library that brings the Model-View-Update (MVU) pattern to .NET MAUI development. While MVU has been discussed for years, it gained widespread attention through ReactJS and later influenced frameworks like React Native and Flutter.
Note
For more details on MVU approach of MauiReactor https://adospace.gitbook.io/mauireactor/components/stateful-components.
Advantages of MVU
- Predictable state management through immutable state
- Simplified hot reload implementation
- Full C# IDE support for UI development
- Potentially better performance characteristics
The library started as an experiment with Xamarin.Forms nearly ten years ago. Over time, it evolved into what is now MauiReactor, incorporating lessons learned from real-world application development and the feedback from like-minded developers who wanted to explore MVU patterns within the .NET ecosystem. The library is designed as a thin layer over .NET MAUI, with much of the code being auto-generated. This approach keeps the codebase relatively simple and makes it easier to update when new versions of .NET MAUI are released.
Today, MauiReactor is a mature open-source project with over 650 stars on GitHub. I use and maintain it across dozens of production applications, and my clients have consistently expressed appreciation for the significantly reduced development time required to build applications with MauiReactor compared to standard .NET MAUI.
MVU vs MVVM
.NET MAUI naturally supports the MVVM pattern, which many developers appreciate for its familiarity and the ability to reuse existing WPF knowledge. MVVM offers excellent separation of concerns, and good tooling support. For teams with existing MVVM expertise, this remains a valid choice.
MauiReactor provides an alternative for developers who want to explore MVU patterns or those coming from React Native or Flutter backgrounds who are comfortable with declarative UI approaches written in code.
MauiReactor’s MVU approach offers some key advantages over MVVM:
1. Enhanced Productivity
You can write UI logic directly in the view component without requiring commands to handle user interactions or custom value converters to pass correct values to widgets. For example in MVVM, to implement a user login you need a LoginCommand object to handle the “Login” button click, specify a “CanExecute” callback to enable it only after the user has entered username/password strings, and additional code to show a busy indicator during command execution. In MVU, you can write a Button in C# and specify a callback for the OnClick event, then set the IsEnabled property to true when the username/password fields are filled.
Button("Login")
.IsEnabled(!string.IsNullOrWhiteSpace(State.FirstName) && !string.IsNullOrWhiteSpace(State.LastName))
.OnClicked(OnLogin)
2. More Flexible Conditional UI
Consider a login component that displays two text entries and a login button, with a busy indicator during authentication. In MVVM, you need value converters to show or hide widgets. In MVU with MauiReactor, you can check a state property and use a simple evaluation statement to show different UI elements (similar to Blazor).
ActivityIndicator()
.IsVisible(State.IsBusy)
State.IsBusy
? ActivityIndicator()
: null
3. True Hot Reload
Thanks to a different separation of concerns, MauiReactor enables superior hot reload capabilities. In MVVM, you typically have View separated from logic (ViewModel) and state (Model), though often developers keep logic and state in a single ViewModel class. In MVU (MauiReactor’s flavor), you have View+Logic separated from Model (State). Keeping state separate from view and logic allows hot reloading the application without losing context. For example, if you have a complex page with text entered in multiple text fields and decide to change some code, when the page is hot-reloaded, the same text remains in the text fields because the state is retained between iterations.
Bringing this all together, here is the typical counter app in MauiReactor.
class CounterPageState
{
public int Counter { get; set; }
}
class CounterPage : Component<CounterPageState>
{
public override VisualNode Render()
=> ContentPage("Counter Sample",
VStack(
Label($"Counter: {State.Counter}"),
Button("Click To Increment", () =>
SetState(s => s.Counter++))
)
);
}
To put it in perspective, this is the counter in React Native which demonstrates a similar MVU implementation.
import React, { useState } from 'react';
import { View, Text, TouchableOpacity } from 'react-native';
const Counter = () => {
const [count, setCount] = useState(0);
return (
<View>
<Text>Count: {count}</Text>
<TouchableOpacity onPress={() => setCount(count + 1)}>
<Text>+</Text>
</TouchableOpacity>
<TouchableOpacity onPress={() => setCount(count - 1)}>
<Text>-</Text>
</TouchableOpacity>
<TouchableOpacity onPress={() => setCount(0)}>
<Text>Reset</Text>
</TouchableOpacity>
</View>
);
};
export default Counter;
Real-World Usage
MauiReactor has been used in production applications across different domains. A notable example includes work with Real Time Research, Inc., a US company that provides data and analytics solutions for natural resources to government agencies conducting scientific research. Their applications allow researchers to collect field data in offline environments, synchronize with Microsoft Azure, and generate reports through Microsoft Power BI.
Demo application
MauiReactor powers the Real Time Research application used by researchers to conduct fish surveys, collecting data about fish and habitat measurements. This application connects to a passive integrated transponder (PIT) tag reader, retrieves GPS location data, and reads barcodes.
These applications handle complex data workflows and need to work reliably in challenging field conditions. The choice to use MauiReactor was based on specific technical requirements around state management and offline synchronization patterns.
Here’s an example of how state management works during synchronization.
async Task OnSync()
{
SetState(s =>
{
s.IsBusy = true;
s.SyncProgress = null;
});
try
{
_syncProviderService.SyncProgress += SyncProviderService_SyncProgress;
await _syncAgentService.Synchronize();
Preferences.Set("LastSync", DateTime.Now);
}
catch (Exception ex)
{
await ContainerPage.DisplayAlert("The application is unable to sync with the server.", "OK");
}
finally
{
_syncProviderService.SyncProgress -= SyncProviderService_SyncProgress;
SetState(s =>
{
s.IsBusy = false;
s.SyncProgress = null;
});
}
}
void SyncProviderService_SyncProgress(object? sender, SyncProgressEventArgs e)
{
SetState(_ =>
{
_.SyncStage = e.Stage;
_.SyncProgress = e.Progress;
});
}
Beyond enterprise applications, the library has been used in various consumer-oriented projects, showing its flexibility across different application types and requirements.
Technical Characteristics
MauiReactor has several technical characteristics that may be relevant depending on your project needs:
Hot Reload Implementation
The MVU architecture makes hot reload more straightforward to implement. Since the UI is a function of state, reloading involves swapping the view and update functions while preserving the application state. This can lead to faster development cycles, particularly when working on complex UI interactions.
MauiReactor’s hot reload works with the standard .NET CLI (with its limitations) or by installing a custom .NET tool (Reactor.Maui.HotReload). With the dedicated tool, MauiReactor supports almost any change you make to the code and operates quickly. For example, you can create new classes, remove or add methods, and change types and properties. Any time you modify a file, it’s compiled on the fly and sent to the application. It works with Android, iOS, Mac, and Windows, using the emulator or real device. You can develop in Visual Studio, VS Code, and Rider under Windows or Mac.
The hot reload function swaps the entire assembly running the app with the one containing the new code (not just the view of the current component). The entire application gets the new code, but the current page, component, and context are preserved because they’re linked to the state class that is copied over. You’ll rarely be required to restart the app, providing a great development experience.
Code-Based UI Development
Applications are written entirely in C#, which means you can use all the IDE features you’re familiar with: IntelliSense, refactoring, debugging, and code analysis tools. This approach also works consistently across different development environments.
Theming
XAML styling continues to work, so you can port your existing XAML-based resources. However, MauiReactor also features a powerful theming system that allows you to declare styles directly in C# to customize any controls, standard or imported from other libraries.
public class AppTheme : Theme
{
protected override void OnApply()
{
LabelStyles.Default = _ => _
.FontFamily("OpenSansRegular")
.FontSize(FontNormal)
.TextColor(Black);
LabelStyles.Themes["Title"] = _ => _
.FontSize(20);
}
}
For more information about theming, visit the MauiReactor theming documentation.
Data Binding
One thing you won’t miss in MauiReactor is building value converters for everything. Coming from the XAML world, you’re used to creating value converters to transform data read from view models (like InverseBooleanConverter or BoolToVisibilityConverter).
<Label IsVisible="{Binding MyValue, Converter={StaticResource InvertedBoolConverter}}"/>
MauiReactor can set the property using normal C# code, allowing you to transform state property values or evaluate functions:
Label().IsVisible(!MyValue)
Label().IsVisible(IsLabelVisible())
Conditional rendering
Conditional rendering of UI portions using plain XAML can be cumbersome: you probably need to use a converter and show/hide controls. Without worry over performance implications of creating controls that may never appear, MauiReactor allows you to simply declare your UI in C# with custom logic right in the component:
class LoginPage : Component<LoginPageState>
{
public override VisualNode Render()
=> ContentPage(
State.IsLoggingIn ?
RenderBusyUI()
:
RenderLoginUI()
);
}
Readability
Large pages can be broken down into simpler render functions or arranged into simpler components. Readability over XAML is one of the core features of code-based UIs and becomes apparent as soon as you start integrating MauiReactor into an existing .NET MAUI application.
class HomePage : Component
{
public override VisualNode Render()
=> ContentPage(
Grid("Auto,*,Auto", "*",
RenderHeader(),
RenderBody(),
RenderFooter()
);
}
Testing
MauiReactor fully supports integration testing by providing utility classes and functions that let you create components on the fly without requiring platform initialization. For example, this is how you can test the counter component shown above.
[Test]
public void CounterWithServicePage_Clicking_Button_Correctly_Increments_The_Counter()
{
using var serviceContext = new ServiceContext(services => services.AddSingleton<IncrementService>());
var mainPageNode = TemplateHost.Create(new CounterWithServicePage());
// Check that the counter is 0
mainPageNode.Find<MauiControls.Label>("Counter_Label")
.Text
.ShouldBe($"Counter: 0");
// Click on the button
mainPageNode.Find<MauiControls.Button>("Counter_Button")
.SendClicked();
// Check that the counter is 1
mainPageNode.Find<MauiControls.Label>("Counter_Label")
.Text
.ShouldBe($"Counter: 1");
}
MauiReactor is test framework agnostic: continue to use MSTest, XUnit, NUnit, or any other testing framework you prefer.
Performance Considerations
Applications built with MauiReactor avoid some of the overhead associated with XAML parsing and reflection-based data binding. In practice, this can result in better startup performance and runtime efficiency, though the actual impact depends on your specific application design.
MauiReactor pages are compiled by default, while poorly designed XAML-based pages may contain performance bottlenecks related to reflection-based bindings and resource lookups. This usually results in faster page opening times and smoother transitions. On the other hand, MauiReactor may require more rendering passes every time you edit a state property.
In my experience, performance issues are rarely related to the framework itself (.NET MAUI vs MauiReactor) but more often to poorly designed pages or logic code (including hidden exceptions or abnormal thread context switches).
If you’re serious about performance optimizations and want to test differences between .NET MAUI and MauiReactor, I’ve prepared a sample project that can help measure startup times of your application.
Note
The library’s minimal use of reflection makes it well-suited for Ahead-of-Time compilation scenarios, which can be important for certain deployment requirements.
Easy Third-Party Component Integration
MauiReactor can work with existing .NET MAUI component libraries from vendors like Syncfusion, Telerik, and DevExpress. Custom controls can be integrated with some additional wrapper code.
Using an existing control in MauiReactor usually requires adding this kind of code to your project:
[Scaffold(typeof(Syncfusion.Maui.Buttons.ToggleButton))]
partial class ToggleButton { }
MauiReactor source generators will create the required layer of code. The imported widget can then be instantiated inside a component:
VStack(
new ToggleButton()
.OnClicked(()=>...)
)
For certain, more complex controls, the integration may require additional code. I constantly create and update integration code for common libraries in this repository.
Looking Forward
MauiReactor represents one approach to cross-platform development within the .NET ecosystem. It’s not intended to replace existing patterns, but rather to provide an alternative for developers who find MVU patterns better suited to their needs or development style. The library continues to evolve based on feedback from developers using it in production scenarios. As the .NET MAUI ecosystem grows, having different approaches available gives developers more options to choose what works best for their specific requirements.
I’ve been in the software development industry since 2001, and I’ve witnessed the rise of the .NET platform over the years. I’ve enjoyed the beauty of the WPF architecture and MVVM pattern (I’m the original author of the AvalonDock library). Then came ReactJS with its unique MVU approach to writing UI for the web. I probably never wrote a Xamarin.Forms application in XAML and MVVM, but I soon started to think that it could be possible to write cross-platform .NET applications in MVU thanks to the flexibility of the C# language.
Over the last 10 years, I’ve written many C#-based MauiReactor applications and loved their simplicity and productivity. I never looked back.
Thanks to the wonderful .NET community, I’ve received hints and bug fixes that have transformed what was my personal side project into an established open-source project known and enjoyed by many developers around the world to build their applications faster.
Besides maintaining the core project, given the architecture of MauiReactor, I’m also experimenting with integration with other UI frameworks like WPF, Avalonia, or UNO, and also DrawnUI and Spice.
Create your first MauiReactor app today
- Install the dotnet new template
dotnet new install Reactor.Maui.TemplatePack
- Create a new project
dotnet new maui-reactor-startup -o my-new-project
- Run the application
dotnet build -t:Run -f net9.0-android
For more info, documentation and samples please visit MauiReactor Repo, MauiReactor Samples Repository, and MauiReactor documentation.
The post MauiReactor: An MVU Approach for .NET MAUI appeared first on .NET Blog.