.NET MAUI: MVVM, Dependency Injection & Platform Code
MVVM with CommunityToolkit.Mvvm
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
public partial class MainViewModel : ObservableObject
{
// [ObservableProperty] generates the full property + backing field +
// PropertyChanged notification -- no manual INotifyPropertyChanged boilerplate
[ObservableProperty]
private string userName = string.Empty;
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(SaveCommand))]
private bool isBusy;
// Bound UI automatically reflects Add/Remove via CollectionChanged
public ObservableCollection<Item> Items { get; } = new();
// [RelayCommand] generates an ICommand-wrapped property (SaveCommand)
[RelayCommand(CanExecute = nameof(CanSave))]
private async Task SaveAsync()
{
IsBusy = true;
try
{
await _dataService.SaveAsync(UserName);
}
finally
{
IsBusy = false;
}
}
private bool CanSave() => !IsBusy && !string.IsNullOrWhiteSpace(UserName);
private readonly IDataService _dataService;
public MainViewModel(IDataService dataService)
{
_dataService = dataService;
}
}Dependency Injection Setup
// MauiProgram.cs -- app composition root, same DI container as ASP.NET Core
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
});
builder.Services.AddSingleton<IDataService, DataService>();
// Register page + ViewModel -- resolving MainPage auto-injects
// MainViewModel, which auto-injects IDataService
builder.Services.AddTransient<MainPage>();
builder.Services.AddTransient<MainViewModel>();
return builder.Build();
}
}
// MainPage.xaml.cs
public partial class MainPage : ContentPage
{
public MainPage(MainViewModel viewModel)
{
InitializeComponent();
BindingContext = viewModel;
}
}Platform-Specific Code
// Conditional compilation for platform-specific APIs
#if IOS
using UIKit;
#elif ANDROID
using Android.Widget;
#endif
public void ConfigureStatusBar()
{
#if IOS
UIApplication.SharedApplication.StatusBarStyle = UIStatusBarStyle.DarkContent;
#elif ANDROID
// Android-specific status bar handling
#endif
}
// Runtime platform check (no conditional compilation needed)
if (DeviceInfo.Platform == DevicePlatform.iOS)
{
// iOS-only runtime behavior
}
// Fluent per-platform configuration on certain elements
entry.On<iOS>().SetKeyboard(Keyboard.Email);Single-Project Structure
One project multi-targets net8.0-ios, net8.0-android, net8.0-maccatalyst, net8.0-windows -- no separate per-platform head projects like Xamarin.Forms required.
Platform-specific source files live under Platforms/iOS, Platforms/Android, etc., only compiled into their respective target.
One source app icon/splash screen/font (Resources/AppIcon/appicon.svg) is auto-resized and configured per platform at build time via MauiIcon/MauiSplashScreen/MauiFont build actions.
Handlers map cross-platform controls to real native widgets per platform (UIButton on iOS, Android.Widget.Button on Android) -- native look and feel, not a custom-drawn UI toolkit.
Keep your own version of these notes — editable, searchable, and organised by your stack.
Start free