Why MVVM in MAUI?
MVVM separates an app into three layers: the Model (plain data and business rules), the View (XAML markup describing layout), and the ViewModel (the mediator that exposes bindable properties and commands the View binds to). This keeps navigation and rendering logic out of business code, avoids stuffing logic into code-behind event handlers, and makes the app testable because a ViewModel can be unit tested with no UI framework loaded at all.
Cricket analogy: MVVM is like separating a cricket team's captain, who makes tactical decisions, from the players' raw stats and the scoreboard that just displays numbers, so a captain like MS Dhoni's calls never get tangled with how the scoreboard renders.
The Three Layers: Model, View, ViewModel
In practice, the View is a .xaml file with no business logic beyond visual state; the Model is plain classes or a repository representing data (a Person class, a database record); and the ViewModel exposes only what the View needs — formatted strings, ObservableCollections, and ICommand properties — while never holding a direct reference back to a Page or control, which keeps it unit-testable and reusable across pages.
Cricket analogy: The View is like the stadium scoreboard, the Model is the raw ball-by-ball data feed, and the ViewModel is the analyst who decides which stats to surface, mirroring how a MAUI ViewModel exposes only what the View needs.
ObservableObject and [ObservableProperty]
Writing INotifyPropertyChanged boilerplate by hand for every property gets repetitive fast. The CommunityToolkit.Mvvm package's ObservableObject base class combined with the [ObservableProperty] source-generator attribute lets you declare a private field like _name and have the toolkit generate the public Name property, backing field access, and PropertyChanged notification automatically at compile time.
Cricket analogy: The [ObservableProperty] source generator is like a groundstaff crew that automatically repaints the boundary rope after every over, so nobody manually redoes the property-changed boilerplate that INotifyPropertyChanged used to require by hand.
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
public partial class ProfileViewModel : ObservableObject
{
[ObservableProperty]
private string name;
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(SaveCommand))]
private bool isBusy;
[RelayCommand(CanExecute = nameof(CanSave))]
private async Task Save()
{
IsBusy = true;
await Task.Delay(500); // simulate saving to a repository
IsBusy = false;
}
private bool CanSave() => !IsBusy && !string.IsNullOrWhiteSpace(Name);
}Naming convention matters: a private field named name (lowercase) generates a public Name property, and a private field named _isBusy generates IsBusy. The generator strips a leading underscore, so pick field names deliberately to get the public API you expect.
Keeping ViewModels Testable
A well-formed ViewModel never holds a direct reference to a Page, Button, or other UI element — instead it exposes plain properties and ICommand objects that the View binds to. This means a unit test can instantiate ProfileViewModel, call SaveCommand.Execute(null), and assert on IsBusy or Name without ever spinning up the MAUI runtime, which is the practical payoff of following MVVM rather than treating it as pure ceremony.
Cricket analogy: A testable ViewModel is like a batting simulator that can be tested with any bowling machine input, without needing the real Wankhede Stadium set up, the same way a ViewModel can be unit tested without launching the actual app.
Avoid injecting Page, NavigationPage, or a specific control into a ViewModel's constructor just to call DisplayAlert or navigate. This couples the ViewModel to the MAUI UI layer and breaks the testability MVVM is meant to provide — use an abstraction like INavigationService or a messaging pattern instead.
- MVVM separates concerns into Model (data), View (XAML), and ViewModel (bindable mediator with no direct UI reference).
- The View binds to ViewModel properties and commands; it should contain no business logic.
- CommunityToolkit.Mvvm's ObservableObject and [ObservableProperty] eliminate INotifyPropertyChanged boilerplate.
- [RelayCommand] generates an ICommand-backed property from a plain method, optionally with a CanExecute predicate.
- A ViewModel should never hold a direct reference to a Page or control to remain unit-testable.
- NotifyCanExecuteChangedFor keeps a command's enabled state in sync with a related observable property.
- Testability is MVVM's practical payoff: ViewModels can be tested without loading the MAUI runtime.
Practice what you learned
1. In MVVM, which layer should never hold a direct reference to a Page or Button control?
2. What does the [ObservableProperty] attribute generate from a private field named isBusy?
3. What does [RelayCommand] applied to a method generate?
4. Why is keeping business logic out of code-behind considered a core MVVM practice?
5. What is the purpose of NotifyCanExecuteChangedFor(nameof(SaveCommand))?
Was this page helpful?
You May Also Like
Data Binding Basics
Learn how MAUI connects UI controls to data using the BindingContext and the {Binding} markup extension, including binding modes and INotifyPropertyChanged.
Converters and Commands
Learn how IValueConverter transforms bound data for display and how ICommand/RelayCommand lets buttons and gestures invoke ViewModel logic through bindings.
Collections and CollectionView
Learn how CollectionView renders scrollable lists and grids in .NET MAUI, binding to ObservableCollection and customizing item layout with DataTemplate.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics