.NET MAUI
02 / 02

MVVM, Dependency Injection & Platform Code

.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