Skip to content
NLNuvyntra Labs

Documentation

Introduction

What MVVMExpress is, why it exists, the MIT license, design principles, and how these docs are organized.

Status

Product name is MVVMExpress (MVVM + Express). Package prefix is Plugin.Maui.MVVMExpress. Core targets net10.0 and does not reference Microsoft.Maui.Controls. Manual INotifyPropertyChanged and hand-written commands are first-class; generators are an accelerator, not a requirement.

License

MVVMExpress is MIT licensed. There is no community-versus-commercial split and no revenue threshold. You may use the packages in commercial apps without a paid framework license. The SPDX identifier is MIT; LICENSE lives at the repository root and is packed with each nupkg.

That is a product choice, not a jab at other frameworks. If you already pay for a commercial MVVM stack, keep it. MVVMExpress exists so a MAUI team can take ViewModels, async state, and Shell or page navigation without a second license conversation.

Why this exists

A production MAUI app needs more than INotifyPropertyChanged and ICommand: ViewModel lifecycle bound to page lifetime, async work with cancellation, timeout, retry, and busy state, strongly typed navigation, UI state richer than a boolean IsBusy, and testable ViewModels with no static MAUI calls.

  • CommunityToolkit.Mvvm covers properties, commands, and messaging.
  • Prism.Maui covers page navigation and dialogs. It does not support Shell.
  • ReactiveUI covers observable pipelines and activation, and requires System.Reactive.

None of those, alone, is a MAUI-first operation + state + scope framework. Combining all three creates package, namespace, and mental-model collisions. MVVMExpress is that shell. It is not a fork of those libraries. Capability work — captive portal, HTTP cache, offline sync, form XAML, flags, deep links — stays in focused MauiEssentials plugins.

Design principles

  1. Core is UI-framework-free. Plugin.Maui.MVVMExpress.Core must not reference Microsoft.Maui.Controls.
  2. Optional means optional. Navigation, dialogs, validation, reactive, pagination, and generators are separate packages.
  3. Interfaces at the ViewModel boundary. ViewModels depend on INavigator, IDialogs, IMainThread, IConnectivityProbe — never on Shell, Page.DisplayAlert, or MainThread statics.
  4. Async-first, cancellation-first. Every public async API accepts CancellationToken.
  5. No hidden global state. No static service locator in Core.
  6. Compose MauiEssentials. Connectivity, cache, offline, permissions, flags, deep links, and secure session are adapter surfaces, not new engines.
  7. Source generators are an accelerator, not a requirement.
  8. AOT and trimming are default constraints. Reflection-based registration is a debug fallback.
  9. Do not silently swallow exceptions. Every catch transforms to Outcome, calls IErrorSink, logs and rethrows, or maps OperationCanceledException to Cancelled.
  10. One window is not the app. Navigation, dialogs, and scopes are keyed by window context, not Application.Current.MainPage.

How to read these docs

The left nav follows the surfaces a production MAUI app actually touches. Start here for the contract. Getting started is the install-and-wire path, including the first screen and Playground clone. Comparison evaluates CommunityToolkit.Mvvm, Prism.Maui, and ReactiveUI and includes the syntax map. Application model covers ViewModels, commands, DI, messaging, and Reactive. Application shell covers navigation, chat host, dialogs, validation, forms, and lists. Composition and internals explain packages, adapters, platforms, the operation pipeline, tests, and scale. Release covers generators and the shipped roadmap.

  • Shipped in 1.0.0 means types exist and tests exist. Phases 1–7 plus UseAuth, the host-safe navigator, NavigationPage replace-root, and chat-host APIs are complete.
  • 1.0.0 is the SemVer lock. Public 1.x APIs stay source-compatible; breaking changes wait for 2.0.0. Known limitations are accepted 1.0 scope, not remaining product work. Next work is Phase 8 (1.1.0).
  • Type names stay unique so CommunityToolkit.Mvvm or Prism can sit in the same app if you need them.

Naming — no collisions

Core type names must not collide with CommunityToolkit.Mvvm or Prism when both are referenced.

ConceptMVVMExpressAvoid copying
Observable baseObservableModelObservableObject
ViewModel baseViewModel / PageViewModelPrism BindableBase
Sync commandModelCommandRelayCommand, DelegateCommand
Async commandAsyncModelCommandAsyncRelayCommand, ReactiveCommand
Notify attribute[Notify] / [NotifyAlso][ObservableProperty]
MessengerIMessageHubIMessenger, IEventAggregator
NavigationINavigatorINavigationService
DialogsIDialogsIDialogService
Parameterstyped records / IAcceptNavArgs<T> / IAcceptNavQueryINavigationParameters
ResultOutcome / Outcome<T>competing Result<T> packages

The Comparison page has the side-by-side syntax map ([Notify] versus [ObservableProperty], INavigator versus INavigationService). The attribute is [ModelCommand] / [AsyncModelCommand], not [Command].

Feature comparison

Designed product surface, validated 2026-09-01 against CommunityToolkit.Mvvm 8.4, Prism.Maui 9, and ReactiveUI. This table does not claim MVVMExpress is faster than the others. Shipping versus designed is tracked in the repository FEATURE-MATRIX.md.

FeatureMVVMExpressCommunityToolkit.MvvmPrism.MauiReactiveUI
Observable propertiesYesYesYesYes
Commands / async commandsYesYesYes / PartialYes
Source generatorsYes ([Notify], commands, register, routes)YesNoYes
Navigation (Shell or page)Yes (Shell + page)NoYes (page only)Yes
Lifecycle + cancellationYesNoYesYes
Dialogs / toastYesSeparateYesExtensions
ValidationYesYesExtensionsYes
Reactive derived stateYes (CombineLatest; Rx optional)NoNoYes (Rx required)
Pagination + refresh + searchYesNoExtensionsExtensions
Offline / cache abstractionsYes (adapters)NoNoExtensions
Unified AsyncState<T>YesNoNoExtensions
Typed navigation record argsYesNoNo (dictionary / URI)Partial
Memory-leak GC testsYes (VM, command, Button pop, messenger)PartialPartialPartial
Small / mid / large list batchingYes (AddRange)App codeApp codeApp / Rx
Testing packageYesPartialYesYes