<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: NuvyntraLabs</title>
    <description>The latest articles on DEV Community by NuvyntraLabs (@niladri_prasadpadhy_ccee).</description>
    <link>https://dev.to/niladri_prasadpadhy_ccee</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4138260%2Fe149499a-8204-4e2a-b191-6a06912983c9.png</url>
      <title>DEV Community: NuvyntraLabs</title>
      <link>https://dev.to/niladri_prasadpadhy_ccee</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/niladri_prasadpadhy_ccee"/>
    <language>en</language>
    <item>
      <title>MVVMExpress 1.3.0: One Application Shell for .NET MAUI</title>
      <dc:creator>NuvyntraLabs</dc:creator>
      <pubDate>Wed, 23 Sep 2026 09:24:23 +0000</pubDate>
      <link>https://dev.to/niladri_prasadpadhy_ccee/mvvmexpress-130-one-application-shell-for-net-maui-40n9</link>
      <guid>https://dev.to/niladri_prasadpadhy_ccee/mvvmexpress-130-one-application-shell-for-net-maui-40n9</guid>
      <description>&lt;p&gt;A field app needs properties, commands, a loading state, and a typed route to the next screen. Those pieces usually arrive as three frameworks that overlap.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MVVMExpress&lt;/strong&gt; is the shell that keeps them on one registration path. Version &lt;strong&gt;1.3.0&lt;/strong&gt; is on nuget.org. Core targets &lt;code&gt;net10.0&lt;/code&gt; and does not reference &lt;code&gt;Microsoft.Maui.Controls&lt;/code&gt;. The MAUI host calls &lt;code&gt;UseMvvmExpress&lt;/code&gt;. Shared libraries and tests call &lt;code&gt;AddMvvmExpress&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This post is the walkthrough: what 1.3.0 registers, a first ViewModel, navigation, and the pieces Phases 8–10 added on the 1.0 SemVer lock. Public 1.x APIs stay source-compatible. Breaking changes wait for 2.0.0.&lt;/p&gt;

&lt;h2&gt;
  
  
  What MVVMExpress is
&lt;/h2&gt;

&lt;p&gt;MVVMExpress is a modular MVVM framework for &lt;strong&gt;.NET MAUI&lt;/strong&gt; on Android, iOS, Mac Catalyst, and Windows (single-window). MIT licensed. Manual &lt;code&gt;INotifyPropertyChanged&lt;/code&gt; and hand-written commands are first-class. Generators are an accelerator.&lt;/p&gt;

&lt;p&gt;The page owns &lt;code&gt;BindingContext&lt;/code&gt;. The ViewModel never holds &lt;code&gt;Page&lt;/code&gt;. ViewModels depend on &lt;code&gt;INavigator&lt;/code&gt;, never on &lt;code&gt;Shell.Current&lt;/code&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Host package&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://www.nuget.org/packages/Plugin.Maui.MVVMExpress" rel="noopener noreferrer"&gt;&lt;code&gt;Plugin.Maui.MVVMExpress&lt;/code&gt;&lt;/a&gt; 1.3.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Core&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.nuget.org/packages/Plugin.Maui.MVVMExpress.Core" rel="noopener noreferrer"&gt;&lt;code&gt;Plugin.Maui.MVVMExpress.Core&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Registration&lt;/td&gt;
&lt;td&gt;&lt;code&gt;UseMvvmExpress&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Source&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/nuvyntralabs/Plugin.Maui.MVVMExpress" rel="noopener noreferrer"&gt;github.com/nuvyntralabs/Plugin.Maui.MVVMExpress&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docs&lt;/td&gt;
&lt;td&gt;&lt;a href="https://nuvyntralabs.github.io/packages/plugin-maui-mvvmexpress/" rel="noopener noreferrer"&gt;nuvyntralabs.github.io/packages/plugin-maui-mvvmexpress/&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Guides&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://nuvyntralabs.github.io/packages/plugin-maui-mvvmexpress/integration/" rel="noopener noreferrer"&gt;Get started&lt;/a&gt; · &lt;a href="https://nuvyntralabs.github.io/packages/plugin-maui-mvvmexpress/docs/" rel="noopener noreferrer"&gt;How it works&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Install the packages the screen needs. The host package pulls the MAUI registration. Core stays UI-free so a &lt;code&gt;net10.0&lt;/code&gt; test project can construct the same ViewModel.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Package&lt;/th&gt;
&lt;th&gt;What it adds&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.Core&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ObservableModel&lt;/code&gt;, &lt;code&gt;ViewModel&lt;/code&gt;, commands, &lt;code&gt;AsyncState&lt;/code&gt;, &lt;code&gt;Outcome&lt;/code&gt;, &lt;code&gt;IMessageHub&lt;/code&gt;, &lt;code&gt;IModule&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.Navigation&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;UseNavigationPage&lt;/code&gt;, &lt;code&gt;UseShell&lt;/code&gt;, replace-root, modal stack&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.Dialogs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;IDialogs&lt;/code&gt;, toast on &lt;code&gt;Window.AddOverlay&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.Validation&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;DataAnnotations, &lt;code&gt;MustMatch&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.Pagination&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;SnapshotCollection&lt;/code&gt;, &lt;code&gt;PagedCollection&lt;/code&gt;, &lt;code&gt;SearchQuery&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.Reactive&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;CombineLatest&lt;/code&gt; without &lt;code&gt;System.Reactive&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.SourceGenerators&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;[Notify]&lt;/code&gt;, &lt;code&gt;[NotifyDependsOn]&lt;/code&gt;, &lt;code&gt;[RegisterView]&lt;/code&gt;, &lt;code&gt;[Route]&lt;/code&gt;, analyzers &lt;code&gt;MVVME001&lt;/code&gt;–&lt;code&gt;013&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.Testing&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;FakeNavigator&lt;/code&gt;, &lt;code&gt;FakeDialogs&lt;/code&gt;, &lt;code&gt;LeakProbe&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.Templates&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;dotnet new mvvmexpress&lt;/code&gt; and &lt;code&gt;mvvmexpress-page&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Sibling plugins (NetworkMonitor, ApiCache, SecureSession, FormValidation, FeatureFlags, DeepLinks) are wired by the app through Core abstractions. MVVMExpress does not take a &lt;code&gt;PackageReference&lt;/code&gt; on them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Register
&lt;/h2&gt;

&lt;p&gt;DI is &lt;code&gt;Microsoft.Extensions.DependencyInjection&lt;/code&gt;. There is no container package to swap in.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;builder&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UseMauiApp&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseMvvmExpress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseNavigationPage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseDialogs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UseAuth&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;LoginViewModel&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;UseNavigationPage&lt;/code&gt; is the login → replace-root → push host. &lt;code&gt;UseShell&lt;/code&gt; is an equal host for flyout, tabs, and &lt;code&gt;//&lt;/code&gt; routes. Register one of them per window. &lt;code&gt;UseAuth&amp;lt;TChallenge&amp;gt;()&lt;/code&gt; wraps &lt;code&gt;GuardedNavigator&lt;/code&gt;. Leave that wrapper in place. &lt;code&gt;UseSecureSessionAuth()&lt;/code&gt; and &lt;code&gt;UseDeepLinks()&lt;/code&gt; fail closed when the sibling package is missing.&lt;/p&gt;

&lt;p&gt;Call &lt;code&gt;InitializeComponent()&lt;/code&gt; on &lt;code&gt;App&lt;/code&gt; before resolving pages. ViewModels talk to &lt;code&gt;IMainThread&lt;/code&gt;. Leave &lt;code&gt;MainThread&lt;/code&gt; statics out of the ViewModel.&lt;/p&gt;

&lt;p&gt;Generated &lt;code&gt;[RegisterView]&lt;/code&gt; and &lt;code&gt;[Route]&lt;/code&gt; apply from &lt;code&gt;UseMvvmExpress&lt;/code&gt; through a module initializer. An explicit &lt;code&gt;Map&lt;/code&gt; callback stays optional. A feature assembly registers itself with &lt;code&gt;AddModule&amp;lt;T&amp;gt;()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CatalogModule&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IModule&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IServiceCollection&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ICatalog&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Catalog&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddModule&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CatalogModule&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A convention scan of &lt;code&gt;*Page&lt;/code&gt; / &lt;code&gt;*ViewModel&lt;/code&gt; is outside the 1.3 path. &lt;code&gt;MVVME010&lt;/code&gt; reports that in DEBUG.&lt;/p&gt;

&lt;h2&gt;
  
  
  A ViewModel
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;ObservableModel.SetProperty&lt;/code&gt; compares with &lt;code&gt;EqualityComparer&amp;lt;T&amp;gt;.Default&lt;/code&gt; and skips the event when the value is unchanged. &lt;code&gt;NotifyDependsOn&lt;/code&gt; raises a named set of dependents. Prefer that over &lt;code&gt;PropertyChanged(null)&lt;/code&gt;, which refreshes every binding.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;partial&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NameViewModel&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ViewModel&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Notify&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;_first&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Notify&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;_last&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;NotifyDependsOn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;First&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Last&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;FullName&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;First&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Last&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Types that use &lt;code&gt;[Notify]&lt;/code&gt; or &lt;code&gt;[AsyncModelCommand]&lt;/code&gt; stay &lt;code&gt;partial&lt;/code&gt;. &lt;code&gt;[NotifyDependsOn]&lt;/code&gt; does not need the Reactive package. Hand-written &lt;code&gt;SetProperty&lt;/code&gt; remains valid.&lt;/p&gt;

&lt;p&gt;Lifecycle on &lt;code&gt;ViewModel&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Construct (DI)
  → Accept(args) / Accept(query)
  → InitializeAsync(token)          once
  → OnNavigatedToAsync(token)
  → OnAppearingAsync(token)
  → OnDisappearingAsync(token)
  → OnNavigatedFromAsync(token)
  → Dispose                         cancels ViewModelCancellationToken
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The token is created in the constructor and cancelled on &lt;code&gt;Dispose&lt;/code&gt;. After dispose it stays readable, with &lt;code&gt;IsCancellationRequested&lt;/code&gt; true, so a late continuation can still observe cancel. &lt;code&gt;AutoAttachLifecycle&lt;/code&gt; (default true) attaches appear and disappear. Dispose is the guaranteed cancel.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;AsyncState&amp;lt;T&amp;gt;&lt;/code&gt; is the bindable status object: &lt;code&gt;Status&lt;/code&gt;, &lt;code&gt;Data&lt;/code&gt;, &lt;code&gt;Error&lt;/code&gt;, plus &lt;code&gt;IsLoading&lt;/code&gt;, &lt;code&gt;IsRefreshing&lt;/code&gt;, &lt;code&gt;IsEmpty&lt;/code&gt;, &lt;code&gt;HasError&lt;/code&gt;, and &lt;code&gt;IsSuccess&lt;/code&gt;. &lt;code&gt;LoadAsync&lt;/code&gt; returns &lt;code&gt;Outcome&amp;lt;T&amp;gt;&lt;/code&gt;. The name is &lt;code&gt;Outcome&lt;/code&gt; so it does not collide with an app-level &lt;code&gt;Result&amp;lt;T&amp;gt;&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;AsyncState&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IReadOnlyList&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Products&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LoadAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ListAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bind &lt;code&gt;ItemsSource&lt;/code&gt; to &lt;code&gt;Products.Data&lt;/code&gt; and &lt;code&gt;IsRefreshing&lt;/code&gt; to &lt;code&gt;Products.IsRefreshing&lt;/code&gt;. The host package ships &lt;code&gt;AsyncStateView&lt;/code&gt; for loading, empty, error, and success templates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Commands
&lt;/h2&gt;

&lt;p&gt;The page binds &lt;code&gt;Button.Command&lt;/code&gt;. The ViewModel owns the work, &lt;code&gt;CanExecute&lt;/code&gt;, and cancellation. Tests call &lt;code&gt;ExecuteAsync&lt;/code&gt; without a visual tree.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;AsyncModelCommand&lt;/code&gt; runs through &lt;code&gt;IOperationExecutor&lt;/code&gt;: &lt;code&gt;CanExecute&lt;/code&gt;, a concurrency gate, optional timeout, optional retry, optional debounce or throttle, then the delegate. &lt;code&gt;ICommand.Execute&lt;/code&gt; does not throw. Failures go to &lt;code&gt;IErrorSink&lt;/code&gt; or &lt;code&gt;IDialogs&lt;/code&gt;. &lt;code&gt;ExecuteAsync&lt;/code&gt; still rethrows.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;SaveCommand&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;AsyncModelCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;SaveAsync&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;CanSave&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;AsyncCommandOptions&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Concurrency&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ConcurrencyMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Prevent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Timeout&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromSeconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;15&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;RetryCount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;RetryDelay&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromSeconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ConcurrencyMode&lt;/code&gt; is &lt;code&gt;Prevent&lt;/code&gt;, &lt;code&gt;CancelPrevious&lt;/code&gt;, &lt;code&gt;Queue&lt;/code&gt;, &lt;code&gt;Allow&lt;/code&gt;, or &lt;code&gt;Replace&lt;/code&gt;. Call &lt;code&gt;NotifyCanExecuteChanged&lt;/code&gt; when the condition changes. &lt;code&gt;CanExecuteChanged&lt;/code&gt; is a weak event, so a button on a popped page does not pin the command. Allocate the command once.&lt;/p&gt;

&lt;p&gt;Search text debounce stays on &lt;code&gt;SearchQuery&lt;/code&gt; (default 300 ms, minimum length 2). Command debounce is a separate option on &lt;code&gt;AsyncCommandOptions&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Navigation
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;INavigator&lt;/code&gt; is host-agnostic. Every public method returns &lt;code&gt;Outcome&lt;/code&gt; and accepts &lt;code&gt;CancellationToken&lt;/code&gt;. Navigators hop to &lt;code&gt;IMainThread&lt;/code&gt; before &lt;code&gt;new Page()&lt;/code&gt; or &lt;code&gt;Shell.GoToAsync&lt;/code&gt;. An off-thread factory throws.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Host&lt;/th&gt;
&lt;th&gt;When&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;UseNavigationPage&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Login, then &lt;code&gt;ResetAsync&lt;/code&gt; / &lt;code&gt;ReplaceRootAsync&lt;/code&gt; so Back cannot return to login. Replaces &lt;code&gt;window.Page&lt;/code&gt; with a &lt;code&gt;NavigationPage&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;UseShell&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Flyout, tabs, or an existing Shell. &lt;code&gt;ResetAsync&lt;/code&gt; works when the destination is a root &lt;code&gt;ShellContent&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;One host per window&lt;/td&gt;
&lt;td&gt;A second call is for a second window.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Typed records are the default argument. The destination implements &lt;code&gt;IAcceptNavArgs&amp;lt;T&amp;gt;&lt;/code&gt;. &lt;code&gt;Accept&lt;/code&gt; runs before &lt;code&gt;InitializeAsync&lt;/code&gt;. A dictionary query exists for deep links through &lt;code&gt;IAcceptNavQuery&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;ProductDetailsArgs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;ProductId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NavigateToAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ProductDetailsViewModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ProductDetailsArgs&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;42&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;PushModalAsync&lt;/code&gt; / &lt;code&gt;PopModalAsync&lt;/code&gt; live on &lt;code&gt;IPageNavigator&lt;/code&gt;. Toast is an overlay, drawn with &lt;code&gt;Window.AddOverlay&lt;/code&gt;. It does not wrap &lt;code&gt;Page.Content&lt;/code&gt;, so a later &lt;code&gt;ResetAsync&lt;/code&gt; cannot restore a stale tree.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;[RequiresAuth]&lt;/code&gt; on a ViewModel sends an anonymous session to the challenge type from &lt;code&gt;UseAuth&amp;lt;TChallenge&amp;gt;()&lt;/code&gt;. Production tokens stay on &lt;a href="https://nuvyntralabs.github.io/packages/plugin-maui-secure-session/" rel="noopener noreferrer"&gt;Plugin.Maui.SecureSession&lt;/a&gt;. The template’s demo sign-in is &lt;code&gt;demo@mvvmexpress.dev&lt;/code&gt; / &lt;code&gt;secret&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What 1.3.0 adds
&lt;/h2&gt;

&lt;p&gt;Phases 8–10 sit on the 1.0 lock. Existing &lt;code&gt;UseAuth&lt;/code&gt;, &lt;code&gt;UseNavigationPage&lt;/code&gt;, and &lt;code&gt;Map&amp;lt;TViewModel, TPage&amp;gt;&lt;/code&gt; calls keep compiling.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Phase&lt;/th&gt;
&lt;th&gt;What landed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;One registration path. Generated &lt;code&gt;[RegisterView]&lt;/code&gt; / &lt;code&gt;[Route]&lt;/code&gt; apply without a required &lt;code&gt;Map&lt;/code&gt;. &lt;code&gt;[NotifyDependsOn]&lt;/code&gt;. CommunityToolkit &lt;code&gt;ObservableObject&lt;/code&gt; interop. Analyzers &lt;code&gt;MVVME001&lt;/code&gt;–&lt;code&gt;003&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;IModule&lt;/code&gt; / &lt;code&gt;AddModule&amp;lt;T&amp;gt;()&lt;/code&gt;. Modal stack. &lt;code&gt;UseDeepLinks&lt;/code&gt; / &lt;code&gt;UseSecureSessionAuth&lt;/code&gt; (fail closed). &lt;code&gt;SectionHostView&lt;/code&gt; for in-place tabs. &lt;code&gt;MvvmSearch&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;Analyzers &lt;code&gt;MVVME010&lt;/code&gt;–&lt;code&gt;013&lt;/code&gt;. ILLink roots for Core forms, Navigation, Dialogs, and Pagination. 1.0 contract tests.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Templates and the Marketplace wrappers pin the same 1.3.0 pack. The extensions install &lt;code&gt;Plugin.Maui.MVVMExpress.Templates&lt;/code&gt; and run &lt;code&gt;dotnet new&lt;/code&gt;. They do not copy a private scaffold.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scaffold
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet new &lt;span class="nb"&gt;install &lt;/span&gt;Plugin.Maui.MVVMExpress.Templates
dotnet new mvvmexpress &lt;span class="nt"&gt;-n&lt;/span&gt; MyApp
&lt;span class="nb"&gt;cd &lt;/span&gt;MyApp
dotnet new mvvmexpress-page &lt;span class="nt"&gt;-n&lt;/span&gt; Catalog &lt;span class="nt"&gt;--namespace&lt;/span&gt; MyApp
dotnet &lt;span class="nb"&gt;test &lt;/span&gt;MyApp.Tests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The app template is a &lt;code&gt;NavigationPage&lt;/code&gt; host: a counter page, a login that replace-roots back to home, one snapshot list, one &lt;code&gt;FormViewModel&lt;/code&gt; screen, and a &lt;code&gt;net10.0&lt;/code&gt; test project. After &lt;code&gt;mvvmexpress-page&lt;/code&gt;, call &lt;code&gt;services.AddCatalog()&lt;/code&gt; from &lt;code&gt;MauiProgram&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The same commands are in the IDE: &lt;a href="https://marketplace.visualstudio.com/items?itemName=nuvyntralabs.mvvmexpress" rel="noopener noreferrer"&gt;MVVMExpress on the VS Code Marketplace&lt;/a&gt; and &lt;a href="https://marketplace.visualstudio.com/items?itemName=nuvyntralabs.MVVMExpres-Visual-Studio" rel="noopener noreferrer"&gt;MVVMExpress for Visual Studio 2022+&lt;/a&gt;. After the pack is installed, File → New → Project lists &lt;strong&gt;MVVMExpress MAUI App&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  When something looks wrong
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What you see&lt;/th&gt;
&lt;th&gt;What to do&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;MVVME010&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Stop scanning &lt;code&gt;*Page&lt;/code&gt; / &lt;code&gt;*ViewModel&lt;/code&gt;. Use &lt;code&gt;[RegisterView]&lt;/code&gt; / &lt;code&gt;[Route]&lt;/code&gt;, or an explicit &lt;code&gt;Map&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Page factory throws off the UI thread&lt;/td&gt;
&lt;td&gt;Let the navigator construct the page. It hops to &lt;code&gt;IMainThread&lt;/code&gt; first.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Back returns to login&lt;/td&gt;
&lt;td&gt;Call &lt;code&gt;ResetAsync&lt;/code&gt; or &lt;code&gt;ReplaceRootAsync&lt;/code&gt; after sign-in on &lt;code&gt;UseNavigationPage&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;UseShell&lt;/code&gt; and &lt;code&gt;UseNavigationPage&lt;/code&gt; both registered&lt;/td&gt;
&lt;td&gt;Keep one host per window.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth guard reconstructed by hand&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;UseAuth&amp;lt;TChallenge&amp;gt;()&lt;/code&gt; already wraps &lt;code&gt;GuardedNavigator&lt;/code&gt;. Register &lt;code&gt;IAuthState&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;UseDeepLinks&lt;/code&gt; or &lt;code&gt;UseSecureSessionAuth&lt;/code&gt; throws at startup&lt;/td&gt;
&lt;td&gt;The sibling package is missing. These adapters fail closed.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A popped page stays in memory&lt;/td&gt;
&lt;td&gt;The message handler captured &lt;code&gt;this&lt;/code&gt;. Subscribe with the recipient argument: &lt;code&gt;static (vm, _) =&amp;gt; vm.Refresh()&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;AddGeneratedViewModels&lt;/code&gt; is redundant&lt;/td&gt;
&lt;td&gt;1.3 applies generated maps from &lt;code&gt;UseMvvmExpress&lt;/code&gt;. &lt;code&gt;Map&lt;/code&gt; stays an escape hatch.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Windows opens a second window&lt;/td&gt;
&lt;td&gt;The MAUI host is single-window. Sibling adapters stay Android and iOS.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Where MVVMExpress sits next to the other tools
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Need&lt;/th&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;MAUI application shell: state, commands, typed navigation, dialogs&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;MVVMExpress&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Source-generated properties and commands beside an existing toolkit&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://learn.microsoft.com/dotnet/communitytoolkit/mvvm/" rel="noopener noreferrer"&gt;CommunityToolkit.Mvvm&lt;/a&gt; — interop package ships with 1.3.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Observable pipelines&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://www.reactiveui.net/" rel="noopener noreferrer"&gt;ReactiveUI&lt;/a&gt;, or &lt;code&gt;Plugin.Maui.MVVMExpress.Reactive&lt;/code&gt; when you want &lt;code&gt;CombineLatest&lt;/code&gt; without &lt;code&gt;System.Reactive&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Typed REST on the same host&lt;/td&gt;
&lt;td&gt;&lt;a href="https://nuvyntralabs.github.io/packages/plugin-maui-httpforge/" rel="noopener noreferrer"&gt;Plugin.Maui.HttpForge&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lumina &lt;code&gt;NV*&lt;/code&gt; controls&lt;/td&gt;
&lt;td&gt;&lt;a href="https://nuvyntralabs.github.io/toolkits/nuvyntralabs-uikit/" rel="noopener noreferrer"&gt;NuvyntraLabs.UIKit&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A new MAUI app locked to this shell&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://nuvyntralabs.github.io/toolkits/nuvyn/" rel="noopener noreferrer"&gt;Nuvyn&lt;/a&gt; — &lt;code&gt;nuvyn init&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;CommunityToolkit.Mvvm covers properties and commands. Prism.Maui covers page navigation. ReactiveUI covers observable pipelines. MVVMExpress is the pack when the app needs those three plus bindable async state and a Shell or &lt;code&gt;NavigationPage&lt;/code&gt; host.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet new &lt;span class="nb"&gt;install &lt;/span&gt;Plugin.Maui.MVVMExpress.Templates
dotnet new mvvmexpress &lt;span class="nt"&gt;-n&lt;/span&gt; HarborDesk
&lt;span class="nb"&gt;cd &lt;/span&gt;HarborDesk
dotnet &lt;span class="nb"&gt;test &lt;/span&gt;HarborDesk.Tests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the app, sign in with the template demo account, and confirm Back does not return to login. To walk command, dialog, form, auth, and list in one sample, clone &lt;a href="https://github.com/nuvyntralabs/Plugin.Maui.MVVMExpress/tree/main/samples/Playground" rel="noopener noreferrer"&gt;Playground&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;NuGet: &lt;a href="https://www.nuget.org/packages/Plugin.Maui.MVVMExpress" rel="noopener noreferrer"&gt;Plugin.Maui.MVVMExpress&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Templates: &lt;a href="https://www.nuget.org/packages/Plugin.Maui.MVVMExpress.Templates" rel="noopener noreferrer"&gt;Plugin.Maui.MVVMExpress.Templates&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Repository: &lt;a href="https://github.com/nuvyntralabs/Plugin.Maui.MVVMExpress" rel="noopener noreferrer"&gt;github.com/nuvyntralabs/Plugin.Maui.MVVMExpress&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Get started: &lt;a href="https://nuvyntralabs.github.io/packages/plugin-maui-mvvmexpress/integration/" rel="noopener noreferrer"&gt;nuvyntralabs.github.io/packages/plugin-maui-mvvmexpress/integration/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Comparison: &lt;a href="https://nuvyntralabs.github.io/packages/plugin-maui-mvvmexpress/comparison/" rel="noopener noreferrer"&gt;nuvyntralabs.github.io/packages/plugin-maui-mvvmexpress/comparison/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;By &lt;a href="https://www.linkedin.com/in/niladri-padhy-7ab41626/" rel="noopener noreferrer"&gt;Niladri&lt;/a&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>maui</category>
      <category>mvvm</category>
      <category>opensource</category>
    </item>
    <item>
      <title>HttpForge 1.1.1: A Source-Generated REST Client for .NET MAUI</title>
      <dc:creator>NuvyntraLabs</dc:creator>
      <pubDate>Wed, 23 Sep 2026 09:23:56 +0000</pubDate>
      <link>https://dev.to/niladri_prasadpadhy_ccee/httpforge-111-a-source-generated-rest-client-for-net-maui-3bha</link>
      <guid>https://dev.to/niladri_prasadpadhy_ccee/httpforge-111-a-source-generated-rest-client-for-net-maui-3bha</guid>
      <description>&lt;p&gt;A ViewModel should call &lt;code&gt;GetUser(42)&lt;/code&gt;. It should not assemble &lt;code&gt;HttpRequestMessage&lt;/code&gt; by hand, and it should not discover a wrong route at runtime.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Plugin.Maui.HttpForge&lt;/strong&gt; is that contract. Version &lt;strong&gt;1.1.1&lt;/strong&gt; is on nuget.org. You declare GET, POST, PUT, DELETE, PATCH, and HEAD as a C# interface. A source generator emits the &lt;code&gt;HttpClient&lt;/code&gt; implementation at compile time. There is no reflection request builder.&lt;/p&gt;

&lt;p&gt;This post is the walkthrough: the 1.1 request surface (unchanged in 1.1.1), how to register a typed client, and where retry, cache, tokens, and resumable uploads attach on the same &lt;code&gt;IHttpClientBuilder&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What HttpForge is
&lt;/h2&gt;

&lt;p&gt;HttpForge is the REST contract for &lt;strong&gt;.NET MAUI&lt;/strong&gt; on Android, iOS, Mac Catalyst, and Windows. It targets &lt;code&gt;net10.0&lt;/code&gt;, &lt;code&gt;net10.0-android&lt;/code&gt; (API 21+), &lt;code&gt;net10.0-ios&lt;/code&gt; and &lt;code&gt;net10.0-maccatalyst&lt;/code&gt; (15+), and &lt;code&gt;net10.0-windows10.0.19041.0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It does one job: turn the interface into a client. Resilience, cache, session refresh, and chunked upload stay on their own packages and chain onto the builder &lt;code&gt;AddHttpForgeClient&lt;/code&gt; returns.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Package&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://www.nuget.org/packages/Plugin.Maui.HttpForge" rel="noopener noreferrer"&gt;&lt;code&gt;Plugin.Maui.HttpForge&lt;/code&gt;&lt;/a&gt; 1.1.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Registration&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;UseHttpForge&lt;/code&gt; / &lt;code&gt;AddHttpForgeClient&amp;lt;T&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Source&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/nuvyntralabs/Plugin.Maui.HttpForge" rel="noopener noreferrer"&gt;github.com/nuvyntralabs/Plugin.Maui.HttpForge&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docs&lt;/td&gt;
&lt;td&gt;&lt;a href="https://nuvyntralabs.github.io/packages/plugin-maui-httpforge/" rel="noopener noreferrer"&gt;nuvyntralabs.github.io/packages/plugin-maui-httpforge/&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Guides&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://nuvyntralabs.github.io/packages/plugin-maui-httpforge/integration/" rel="noopener noreferrer"&gt;Get started&lt;/a&gt; · &lt;a href="https://nuvyntralabs.github.io/packages/plugin-maui-httpforge/docs/" rel="noopener noreferrer"&gt;How it works&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;1.1.1 is the current pack. The request surface shipped in 1.1.0. Unsupported shapes fail at compile time (&lt;code&gt;HFG001&lt;/code&gt;–&lt;code&gt;HFG010&lt;/code&gt;). A reflection fallback package is out of scope. Write that one method against &lt;code&gt;HttpClient&lt;/code&gt; yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Declare the API
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IUserApi&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/users/{id}"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/users"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;CreateUser&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;CreateUserRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;42&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Piece&lt;/th&gt;
&lt;th&gt;How&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Methods&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;[Get]&lt;/code&gt;, &lt;code&gt;[Post]&lt;/code&gt;, &lt;code&gt;[Put]&lt;/code&gt;, &lt;code&gt;[Delete]&lt;/code&gt;, &lt;code&gt;[Patch]&lt;/code&gt;, &lt;code&gt;[Head]&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Path&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;/users/{id}&lt;/code&gt; matches &lt;code&gt;int id&lt;/code&gt; or &lt;code&gt;[AliasAs("id")]&lt;/code&gt;. &lt;code&gt;{id?}&lt;/code&gt; is optional&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Query&lt;/td&gt;
&lt;td&gt;Leftover parameters, &lt;code&gt;[Query]&lt;/code&gt;, query objects, collection formats, camel / snake / kebab keys&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flag with no value&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;[QueryName]&lt;/code&gt; writes &lt;code&gt;?archived&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JSON body&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;[Body]&lt;/code&gt;, or &lt;code&gt;[Body(BodySerializationMethod.JsonLines)]&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Headers&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;[Headers("Accept: application/json")]&lt;/code&gt;, &lt;code&gt;[Header("X-Request-Id")]&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multipart&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;[Multipart]&lt;/code&gt; with &lt;code&gt;StreamPart&lt;/code&gt;, &lt;code&gt;ByteArrayPart&lt;/code&gt;, &lt;code&gt;FileInfoPart&lt;/code&gt;, &lt;code&gt;[FormObject]&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Route extras&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;[PathPrefix]&lt;/code&gt;, &lt;code&gt;[Url]&lt;/code&gt;, &lt;code&gt;[Timeout]&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stream&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;IAsyncEnumerable&amp;lt;T&amp;gt;&lt;/code&gt; as JSON Lines or SSE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compression&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;RequestBodyCompression&lt;/code&gt; / &lt;code&gt;[CompressRequest]&lt;/code&gt; (gzip or brotli)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Token attach&lt;/td&gt;
&lt;td&gt;&lt;code&gt;AuthorizationHeaderValueGetter&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rich result&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Task&amp;lt;IApiResponse&amp;lt;T&amp;gt;&amp;gt;&lt;/code&gt; — no throw on 4xx/5xx&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;JSON defaults to &lt;code&gt;System.Text.Json&lt;/code&gt;. A trimmed or AOT host can pass a &lt;code&gt;JsonSerializerContext&lt;/code&gt;. Newtonsoft.Json and XML are optional packages, not part of the core nupkg. &lt;code&gt;XmlContentSerializer&lt;/code&gt; prohibits DTD processing and sets &lt;code&gt;XmlResolver&lt;/code&gt; to null.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Task&amp;lt;T&amp;gt;&lt;/code&gt; throws &lt;code&gt;ApiException&lt;/code&gt; when the server returns 4xx or 5xx, and &lt;code&gt;ApiRequestException&lt;/code&gt; when the transport fails (timeout, DNS, TLS). &lt;code&gt;Task&amp;lt;IApiResponse&amp;lt;T&amp;gt;&amp;gt;&lt;/code&gt; returns status, headers, and body so the caller can branch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Register
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Plugin.Maui.HttpForge&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UseMauiApp&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseHttpForge&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddHttpForgeClient&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IUserApi&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BaseAddress&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://api.example.com"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Resolve &lt;code&gt;IUserApi&lt;/code&gt; from DI. A console or test host can skip MAUI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RestService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;For&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IUserApi&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://api.example.com"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;AddHttpForgeClient&amp;lt;T&amp;gt;()&lt;/code&gt; returns &lt;code&gt;IHttpClientBuilder&lt;/code&gt;. That builder is where handlers attach. HttpForge itself does not reference ApiResilience, ApiCache, SecureSession, or SmartUpload, so a typed client still builds when those packages are absent.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 1.1 request surface
&lt;/h2&gt;

&lt;p&gt;1.1.0 filled the Refit-shaped gaps from 1.0: query objects, collection formats, naming presets, timeouts, runtime URLs, path prefixes, optional segments, valueless query flags, form-object flattening, streaming, request compression, and a token attach hook.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;PathPrefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api/v1"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IUserApi&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/users"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;UserQuery&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CollectionFormat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Multi&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;ages&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/users/{id}/orders/{orderId?}"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5_000&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetOrders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/events"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="n"&gt;IAsyncEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;StreamEvents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UrlParameterKeyFormatter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;UrlParameterKeyFormatter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SnakeCase&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AuthorizationHeaderValueGetter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;tokenStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetAccessTokenAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RequestBodyCompression&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RequestBodyCompression&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Gzip&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;CollectionFormat&lt;/code&gt; is &lt;code&gt;Multi&lt;/code&gt;, &lt;code&gt;Csv&lt;/code&gt;, &lt;code&gt;Ssv&lt;/code&gt;, &lt;code&gt;Tsv&lt;/code&gt;, or &lt;code&gt;Pipes&lt;/code&gt;. &lt;code&gt;[Url]&lt;/code&gt; replaces the method path with a runtime string or &lt;code&gt;Uri&lt;/code&gt;. Validate that value before the call. &lt;code&gt;AuthorizationHeaderValueGetter&lt;/code&gt; sees an absolute URI (&lt;code&gt;BaseAddress&lt;/code&gt; plus the relative path) and attaches a header. A 401 refresh stays on SecureSession or ApiResilience.&lt;/p&gt;

&lt;p&gt;Multipart is one POST:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Multipart&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/users/{id}/photo"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;UploadPhoto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;AliasAs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"file"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="n"&gt;StreamPart&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UploadPhoto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;StreamPart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"photo.jpg"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"image/jpeg"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;[FormObject]&lt;/code&gt; flattens a C# object into form fields (&lt;code&gt;name&lt;/code&gt;, &lt;code&gt;address.city&lt;/code&gt;). &lt;code&gt;HFG008&lt;/code&gt; fires when &lt;code&gt;[FormObject]&lt;/code&gt; is used without &lt;code&gt;[Multipart]&lt;/code&gt;. A file that must resume after process death belongs on &lt;a href="https://nuvyntralabs.github.io/packages/plugin-maui-smart-upload/" rel="noopener noreferrer"&gt;Plugin.Maui.SmartUpload&lt;/a&gt;. Keep HttpForge for the JSON around that file: create the asset, confirm completion.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chain the pipeline
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;IHttpClientFactory&lt;/code&gt; invokes handlers in reverse add order. Add resilience first, then cache, so a CacheFirst hit never enters retry. Register host options first, then attach each typed client.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ViewModel
  → IUserApi (generated)
      → IHttpClientFactory
          → GET cache            (ApiCache, optional)
          → Auth / 401 refresh   (SecureSession or ApiResilience)
          → Retry / circuit      (ApiResilience or Polly)
          → HttpClient
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;builder&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UseMauiApp&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseHttpForge&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseApiResilience&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Retry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MaxRetryAttempts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CircuitBreaker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BreakDuration&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromSeconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;15&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OfflineQueue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Enabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TokenRefresh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Enabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseApiCache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultExpiration&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromMinutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;30&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultPolicy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CachePolicy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CacheFirst&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IAccessTokenProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AuthTokenProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddHttpForgeClient&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IUserApi&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BaseAddress&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://api.example.com"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddApiResilience&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddApiCache&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cached responses include &lt;code&gt;X-ApiCache-Hit&lt;/code&gt;, &lt;code&gt;X-ApiCache-Stale&lt;/code&gt;, and &lt;code&gt;X-ApiCache-Policy&lt;/code&gt;. After a local write, &lt;code&gt;await cache.InvalidateByPrefixAsync("/users")&lt;/code&gt;. Calling &lt;code&gt;IApiCache.GetAsync&lt;/code&gt; around the same HttpForge GET caches twice.&lt;/p&gt;

&lt;p&gt;Pick one 401 path. SecureSession targets Android and iOS: store the refresh token, attach Bearer, retry once. On Mac Catalyst or Windows, use ApiResilience &lt;code&gt;IAccessTokenProvider&lt;/code&gt; and leave SecureSession off that client. A login interface stays without the session handler. The business interface gets it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddHttpForgeClient&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IAuthApi&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BaseAddress&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://api.example.com"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddHttpForgeClient&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IUserApi&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BaseAddress&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://api.example.com"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddSecureSession&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddApiResilience&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When SecureSession owns 401, set &lt;code&gt;options.TokenRefresh.Enabled = false&lt;/code&gt; on ApiResilience. Stacking both refresh loops on one client races the refresh.&lt;/p&gt;

&lt;p&gt;An organization already on Polly can skip ApiResilience and call &lt;code&gt;.AddStandardResilienceHandler()&lt;/code&gt; on the same builder. TLS pinning is &lt;a href="https://nuvyntralabs.github.io/packages/plugin-maui-tls-pin/" rel="noopener noreferrer"&gt;Plugin.Maui.TlsPin&lt;/a&gt;, fail-closed, on that handler chain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Optional packages
&lt;/h2&gt;

&lt;p&gt;HttpForge alone is enough for a typed client. Add a sibling package only when the host needs it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package Plugin.Maui.HttpForge
dotnet add package Plugin.Maui.HttpForge.Testing
dotnet add package Plugin.Maui.HttpForge.NewtonsoftJson
dotnet add package Plugin.Maui.HttpForge.Xml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;StubHttp&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/users/{id}"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;Reply&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;With&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"octocat"&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateClient&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IUserApi&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://api.example.com"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;7&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;VerifyAllCalledAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;settings.ContentSerializer = new NewtonsoftJsonContentSerializer()&lt;/code&gt; or &lt;code&gt;new XmlContentSerializer()&lt;/code&gt; swaps the body codec.&lt;/p&gt;

&lt;h2&gt;
  
  
  When something looks wrong
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What you see&lt;/th&gt;
&lt;th&gt;What to do&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;HFG001&lt;/code&gt;–&lt;code&gt;HFG010&lt;/code&gt; at build&lt;/td&gt;
&lt;td&gt;The interface shape is outside the generator. Change the contract, or write that call with &lt;code&gt;HttpClient&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;HFG008&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;[FormObject]&lt;/code&gt; needs &lt;code&gt;[Multipart]&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ApiException&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The server returned 4xx or 5xx. Switch the method to &lt;code&gt;Task&amp;lt;IApiResponse&amp;lt;T&amp;gt;&amp;gt;&lt;/code&gt; when the caller should branch.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ApiRequestException&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Timeout, DNS, or TLS. The response never arrived. Retry belongs on ApiResilience or Polly.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;[Url]&lt;/code&gt; hits an unexpected host&lt;/td&gt;
&lt;td&gt;Validate the runtime string before the call.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;401 refresh runs twice&lt;/td&gt;
&lt;td&gt;One owner: &lt;code&gt;.AddSecureSession()&lt;/code&gt; or ApiResilience &lt;code&gt;TokenRefresh&lt;/code&gt;, not both.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GET is cached and then cached again&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;.AddApiCache()&lt;/code&gt; is already on the client. Skip &lt;code&gt;IApiCache.GetAsync&lt;/code&gt; for those URLs.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A large upload dies with the process&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;[Multipart]&lt;/code&gt; is one POST. Use SmartUpload for the bytes and HttpForge for the JSON confirm.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SecureSession on Mac Catalyst or Windows&lt;/td&gt;
&lt;td&gt;That plugin is Android and iOS. Use &lt;code&gt;IAccessTokenProvider&lt;/code&gt; on ApiResilience.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Where HttpForge sits next to the other tools
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Need&lt;/th&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Generated REST interface on MAUI&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;HttpForge&lt;/strong&gt; — &lt;code&gt;Plugin.Maui.HttpForge&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Retry, circuit breaker, offline POST queue&lt;/td&gt;
&lt;td&gt;&lt;a href="https://nuvyntralabs.github.io/packages/plugin-maui-api-resilience/" rel="noopener noreferrer"&gt;Plugin.Maui.ApiResilience&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GET CacheFirst / stale-while-revalidate&lt;/td&gt;
&lt;td&gt;&lt;a href="https://nuvyntralabs.github.io/packages/plugin-maui-api-cache/" rel="noopener noreferrer"&gt;Plugin.Maui.ApiCache&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tokens and a single 401 retry on Android and iOS&lt;/td&gt;
&lt;td&gt;&lt;a href="https://nuvyntralabs.github.io/packages/plugin-maui-secure-session/" rel="noopener noreferrer"&gt;Plugin.Maui.SecureSession&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Chunked upload that survives process death&lt;/td&gt;
&lt;td&gt;&lt;a href="https://nuvyntralabs.github.io/packages/plugin-maui-smart-upload/" rel="noopener noreferrer"&gt;Plugin.Maui.SmartUpload&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The same idea on a wider framework matrix&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/reactiveui/refit" rel="noopener noreferrer"&gt;Refit&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Refit is the mature general-purpose client. HttpForge 1.1 matches the request surface teams usually need from Refit 15 — methods, query objects, multipart, &lt;code&gt;IApiResponse&amp;lt;T&amp;gt;&lt;/code&gt;, streaming, compression, and an authorization header getter — and leaves DI inside the core package. Refit remains the default when the team already uses it, needs &lt;code&gt;Refit.Reflection&lt;/code&gt;, or targets frameworks beyond this MAUI matrix. HttpForge is the client when the host should chain ApiResilience, ApiCache, SecureSession, and SmartUpload on one &lt;code&gt;IHttpClientBuilder&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The ViewModel that calls &lt;code&gt;IUserApi&lt;/code&gt; usually lives in &lt;a href="https://nuvyntralabs.github.io/packages/plugin-maui-mvvmexpress/" rel="noopener noreferrer"&gt;MVVMExpress&lt;/a&gt;. &lt;a href="https://nuvyntralabs.github.io/toolkits/nuvyn/" rel="noopener noreferrer"&gt;Nuvyn&lt;/a&gt; &lt;code&gt;init&lt;/code&gt; includes HttpForge on a new MAUI host.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package Plugin.Maui.HttpForge
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Register &lt;code&gt;UseHttpForge&lt;/code&gt;, add &lt;code&gt;IUserApi&lt;/code&gt; with a &lt;code&gt;BaseAddress&lt;/code&gt;, and call &lt;code&gt;GetUser&lt;/code&gt; from a ViewModel. For a test that never leaves the process, add &lt;code&gt;Plugin.Maui.HttpForge.Testing&lt;/code&gt; and stub the route with &lt;code&gt;StubHttp&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;NuGet: &lt;a href="https://www.nuget.org/packages/Plugin.Maui.HttpForge" rel="noopener noreferrer"&gt;Plugin.Maui.HttpForge&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Repository: &lt;a href="https://github.com/nuvyntralabs/Plugin.Maui.HttpForge" rel="noopener noreferrer"&gt;github.com/nuvyntralabs/Plugin.Maui.HttpForge&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Get started: &lt;a href="https://nuvyntralabs.github.io/packages/plugin-maui-httpforge/integration/" rel="noopener noreferrer"&gt;nuvyntralabs.github.io/packages/plugin-maui-httpforge/integration/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Comparison: &lt;a href="https://nuvyntralabs.github.io/packages/plugin-maui-httpforge/comparison/" rel="noopener noreferrer"&gt;nuvyntralabs.github.io/packages/plugin-maui-httpforge/comparison/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;By &lt;a href="https://www.linkedin.com/in/niladri-padhy-7ab41626/" rel="noopener noreferrer"&gt;Niladri&lt;/a&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>maui</category>
      <category>http</category>
      <category>opensource</category>
    </item>
    <item>
      <title>LocalStore 1.1.0: Room-Style CRUD for .NET MAUI, on the Engine You Pick</title>
      <dc:creator>NuvyntraLabs</dc:creator>
      <pubDate>Wed, 23 Sep 2026 09:16:13 +0000</pubDate>
      <link>https://dev.to/niladri_prasadpadhy_ccee/localstore-110-room-style-crud-for-net-maui-on-the-engine-you-pick-49od</link>
      <guid>https://dev.to/niladri_prasadpadhy_ccee/localstore-110-room-style-crud-for-net-maui-on-the-engine-you-pick-49od</guid>
      <description>&lt;p&gt;A MAUI app that writes people to SQLite today should still find those people after the host opens a NuvexaDB file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Plugin.Maui.LocalStore&lt;/strong&gt; is that layer. Version &lt;strong&gt;1.1.0&lt;/strong&gt; is on nuget.org. The host sets &lt;code&gt;StoreBackend&lt;/code&gt;. Application code stays on &lt;code&gt;IStoreCollection&amp;lt;T&amp;gt;&lt;/code&gt;: insert, find, replace, delete, select. A new engine is a different file and the same methods.&lt;/p&gt;

&lt;p&gt;This post is the walkthrough: what 1.1.0 opens, how to register it, the CRUD that stays portable, and the three additions in this release — &lt;code&gt;AutoMigrate&lt;/code&gt;, &lt;code&gt;QueryAsync&lt;/code&gt;, and a source-generated &lt;code&gt;[StoreDao]&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What LocalStore is
&lt;/h2&gt;

&lt;p&gt;LocalStore is a Room-style abstract database for &lt;strong&gt;.NET MAUI&lt;/strong&gt; on Android, iOS, Mac Catalyst, and Windows. MIT licensed. It targets &lt;code&gt;net10.0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It does one job for the host: keep application code on a single collection API while the process opens one embedded engine.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;host always calls
  InsertAsync / FindByIdAsync / ReplaceAsync / DeleteByIdAsync / FindAsync
                    ↓
              IStoreCollection&amp;lt;T&amp;gt;
     ┌────────────┼────────────┐
  SQLite      NuvexaDB     Realm / LiteDB / DuckDB
  SQLCipher   Firebird     LMDB / RocksDB / LevelDB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Package&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://www.nuget.org/packages/Plugin.Maui.LocalStore" rel="noopener noreferrer"&gt;&lt;code&gt;Plugin.Maui.LocalStore&lt;/code&gt;&lt;/a&gt; 1.1.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Registration&lt;/td&gt;
&lt;td&gt;&lt;code&gt;UseMauiLocalStore&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Source&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/nuvyntralabs/Plugin.Maui.LocalStore" rel="noopener noreferrer"&gt;github.com/nuvyntralabs/Plugin.Maui.LocalStore&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docs&lt;/td&gt;
&lt;td&gt;&lt;a href="https://nuvyntralabs.github.io/packages/plugin-maui-local-store/" rel="noopener noreferrer"&gt;nuvyntralabs.github.io/packages/plugin-maui-local-store/&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Guides&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://nuvyntralabs.github.io/packages/plugin-maui-local-store/integration/" rel="noopener noreferrer"&gt;Get started&lt;/a&gt; · &lt;a href="https://nuvyntralabs.github.io/packages/plugin-maui-local-store/docs/" rel="noopener noreferrer"&gt;How it works&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;[StoreDao]&lt;/code&gt; generates a facade over that collection. It is a Room-shaped helper for this package. It is separate from AndroidX Room, and LocalStore does not take a &lt;code&gt;PackageReference&lt;/code&gt; on OfflineSync, JobQueue, or FileVault.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the host picks the engine
&lt;/h2&gt;

&lt;p&gt;Each backend keeps its own file. CRUD on &lt;code&gt;IStoreCollection&amp;lt;T&amp;gt;&lt;/code&gt; stays the same when you change &lt;code&gt;StoreBackend&lt;/code&gt;. Copying rows between those files is a separate step (&lt;code&gt;AutoMigrate&lt;/code&gt; or &lt;code&gt;MigrateAsync&lt;/code&gt;).&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Database&lt;/th&gt;
&lt;th&gt;Kind&lt;/th&gt;
&lt;th&gt;Default file&lt;/th&gt;
&lt;th&gt;On every MAUI platform&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SQLite&lt;/td&gt;
&lt;td&gt;Relational&lt;/td&gt;
&lt;td&gt;&lt;code&gt;app.db&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SQLCipher&lt;/td&gt;
&lt;td&gt;Encrypted SQLite&lt;/td&gt;
&lt;td&gt;&lt;code&gt;app.db&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NuvexaDB&lt;/td&gt;
&lt;td&gt;Document (&lt;code&gt;.nvx&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;app.nvx&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LiteDB&lt;/td&gt;
&lt;td&gt;Document&lt;/td&gt;
&lt;td&gt;&lt;code&gt;app.litedb&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Realm&lt;/td&gt;
&lt;td&gt;Object&lt;/td&gt;
&lt;td&gt;&lt;code&gt;app.realm&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LMDB&lt;/td&gt;
&lt;td&gt;Key-value&lt;/td&gt;
&lt;td&gt;&lt;code&gt;app.lmdb/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes on Android, iOS, and Windows. JSON files on Mac Catalyst&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DuckDB&lt;/td&gt;
&lt;td&gt;Analytical SQL&lt;/td&gt;
&lt;td&gt;&lt;code&gt;app.duckdb&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Native on Windows (&lt;code&gt;win-x64&lt;/code&gt;, &lt;code&gt;win-arm64&lt;/code&gt;). JSON files on Android, iOS, and Mac Catalyst&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Firebird Embedded&lt;/td&gt;
&lt;td&gt;Relational&lt;/td&gt;
&lt;td&gt;&lt;code&gt;app.fdb&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Native on Windows. JSON files elsewhere&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RocksDB&lt;/td&gt;
&lt;td&gt;Key-value&lt;/td&gt;
&lt;td&gt;&lt;code&gt;app.rocksdb/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Native on Windows x64. JSON files elsewhere&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LevelDB&lt;/td&gt;
&lt;td&gt;Key-value&lt;/td&gt;
&lt;td&gt;&lt;code&gt;app.leveldb/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;JSON files on every OS in this pack&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;StoreBackend&lt;/code&gt; defaults to &lt;strong&gt;Nuvexa&lt;/strong&gt;. Set it when you want SQLite or another engine. An empty &lt;code&gt;Path&lt;/code&gt; resolves under &lt;code&gt;LocalApplicationData/Plugin.Maui.LocalStore/&lt;/code&gt; plus the default file name above.&lt;/p&gt;

&lt;p&gt;SQLite, SQLCipher, NuvexaDB, LiteDB, and Realm are the engines that actually run on Android, iOS, Mac Catalyst, and Windows. Pick one of those when the same binary has to use the real database on every head.&lt;/p&gt;

&lt;p&gt;Selecting DuckDB, Firebird, RocksDB, LevelDB, or LMDB on Mac Catalyst still opens. &lt;code&gt;UseMauiLocalStore&lt;/code&gt; does not throw. On an OS without that native library, each row is a JSON file. &lt;code&gt;InsertAsync&lt;/code&gt;, &lt;code&gt;FindByIdAsync&lt;/code&gt;, &lt;code&gt;ReplaceAsync&lt;/code&gt;, &lt;code&gt;DeleteByIdAsync&lt;/code&gt;, and &lt;code&gt;FindAsync&lt;/code&gt; still run. Filters run in memory. &lt;code&gt;EnsureIndexAsync&lt;/code&gt; does nothing. &lt;code&gt;QueryLanguage&lt;/code&gt; is &lt;code&gt;None&lt;/code&gt;. Moving those JSON rows onto a native file later uses the same migrate path as any other engine switch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install and define a document
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package Plugin.Maui.LocalStore
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A document is a POCO with a public &lt;code&gt;string&lt;/code&gt; &lt;code&gt;Id&lt;/code&gt; (nullable is fine) and a public parameterless constructor. Filters use top-level property names (&lt;code&gt;Age&lt;/code&gt;, &lt;code&gt;City&lt;/code&gt;). Get-only properties and &lt;code&gt;[JsonIgnore]&lt;/code&gt; members are left out of the file. Stored scalars are &lt;code&gt;string&lt;/code&gt;, &lt;code&gt;int&lt;/code&gt;, &lt;code&gt;long&lt;/code&gt;, &lt;code&gt;double&lt;/code&gt;, &lt;code&gt;float&lt;/code&gt;, &lt;code&gt;bool&lt;/code&gt;, and &lt;code&gt;DateTime&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Person&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;Age&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Status&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"active"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;City&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;GetCollection&amp;lt;Person&amp;gt;("users")&lt;/code&gt; creates the table or collection on the first write. The same &lt;code&gt;Person&lt;/code&gt; type is valid on every engine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Register
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Plugin.Maui.LocalStore&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UseMauiApp&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseMauiLocalStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Backend&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;StoreBackend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sqlite&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Combine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FileSystem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AppDataDirectory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"app.db"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateIfMissing&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LocalStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Current&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetCollection&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A console or test host can skip the MAUI builder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LocalStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;LocalStoreOptions&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Backend&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;StoreBackend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sqlite&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;services.AddMauiLocalStore(...)&lt;/code&gt; is the DI form of the same options.&lt;/p&gt;

&lt;p&gt;Nuvexa is the default backend. Pass &lt;code&gt;EncryptionKey&lt;/code&gt; when the file is an encrypted &lt;code&gt;.nvx&lt;/code&gt;, a SQLCipher database, a LiteDB password, a Realm file, or a Firebird SYSDBA password. SQLite, DuckDB, LMDB, RocksDB, and LevelDB ignore that key. Keep the secret in &lt;code&gt;SecureStorage&lt;/code&gt; (or the platform keystore). A shipped app keeps it out of source and logs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseMauiLocalStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Backend&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;StoreBackend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Nuvexa&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Combine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FileSystem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AppDataDirectory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"app.nvx"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EncryptionKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateIfMissing&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CacheSizeMb&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;16&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SQLCipher needs its own file. Open &lt;code&gt;app-cipher.db&lt;/code&gt;. Leave an unencrypted &lt;code&gt;app.db&lt;/code&gt; on the SQLite backend.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create, read, update, delete
&lt;/h2&gt;

&lt;p&gt;These calls are the same on every engine. Only &lt;code&gt;StoreBackend&lt;/code&gt; and the path change.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;InsertAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Person&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Ada"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Age&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;36&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Status&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"active"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;City&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"London"&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// Id is generated when Person.Id is null, then written back onto the POCO&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;ada&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FindByIdAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;ada&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Ada Lovelace"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReplaceAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ada&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// a missing Id throws LocalStoreException&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;removed&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DeleteByIdAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// false when the id is absent&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;InsertManyAsync&lt;/code&gt; takes a list and returns the ids. &lt;code&gt;FindAsync()&lt;/code&gt; with no filter returns every row.&lt;/p&gt;

&lt;h2&gt;
  
  
  Select without SQL
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;StoreFilter&lt;/code&gt; and &lt;code&gt;StoreQuery&lt;/code&gt; are the portable query. SQLite and DuckDB run them as SQL when the native engine is open. Nuvexa maps property names onto NQL paths (&lt;code&gt;Age&lt;/code&gt; → &lt;code&gt;age&lt;/code&gt;, &lt;code&gt;Id&lt;/code&gt; → &lt;code&gt;_id&lt;/code&gt;). Key-value engines filter in memory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;adults&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FindAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;StoreFilter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Gte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Age"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;21&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;StoreQuery&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;SortBy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Limit&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;londonActive&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FindAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;StoreFilter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;And&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;StoreFilter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"City"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"London"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;StoreFilter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Status"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"active"&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;StoreQuery&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;SortBy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Age"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SortDescending&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EnsureIndexAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"City"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Status"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Filter&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;StoreFilter.Eq&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Equal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;StoreFilter.Ne&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Not equal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;StoreFilter.Gte&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Greater than or equal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;StoreFilter.Lt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Less than&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;StoreFilter.And&lt;/code&gt; / &lt;code&gt;Or&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;All children / any child&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;StoreQuery.Limit&lt;/code&gt; of &lt;code&gt;0&lt;/code&gt; means no limit. &lt;code&gt;Skip&lt;/code&gt; pages the same filter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Copy a collection onto another engine
&lt;/h2&gt;

&lt;p&gt;Dispose does not copy data. Each engine file stays independent until you ask. On open, &lt;code&gt;AutoMigrate&lt;/code&gt; copies registered collections when the destination collection is empty. &lt;code&gt;Map&amp;lt;T&amp;gt;&lt;/code&gt; is required so both engines read and write the same POCOs. A destination that already has rows is left as it is (&lt;code&gt;Skipped&lt;/code&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseMauiLocalStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Backend&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;StoreBackend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Nuvexa&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Combine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FileSystem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AppDataDirectory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"app.nvx"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EncryptionKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AutoMigrate&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MigrateFrom&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;StoreBackend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sqlite&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;MigrateFrom&lt;/code&gt; is omitted and exactly one other engine file sits next to the destination, that file is the source. Two or more siblings throw until you name &lt;code&gt;MigrateFrom&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You can also copy without replacing &lt;code&gt;LocalStore.Current&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;LocalStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MigrateAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;LocalStoreOptions&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Backend&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;StoreBackend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sqlite&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sqlitePath&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;LocalStoreOptions&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Backend&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;StoreBackend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Nuvexa&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nvxPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;EncryptionKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;StoreMigrationResult&lt;/code&gt; reports &lt;code&gt;From&lt;/code&gt;, &lt;code&gt;To&lt;/code&gt;, &lt;code&gt;Collections&lt;/code&gt;, &lt;code&gt;Documents&lt;/code&gt;, &lt;code&gt;Skipped&lt;/code&gt;, and &lt;code&gt;Reason&lt;/code&gt;. &lt;code&gt;DeleteSourceAfterMigrate&lt;/code&gt; removes the source file after a successful copy. It defaults to false.&lt;/p&gt;

&lt;p&gt;Schema changes inside one engine stay in your code. Adding a property to &lt;code&gt;Person&lt;/code&gt; is your change on that file. Moving &lt;code&gt;users&lt;/code&gt; from SQLite to Nuvexa is the migrate path above.&lt;/p&gt;

&lt;h2&gt;
  
  
  Raw SQL or NQL, when the engine has a language
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;QueryAsync&amp;lt;T&amp;gt;&lt;/code&gt; and &lt;code&gt;ExecuteAsync&lt;/code&gt; sit on &lt;code&gt;ILocalStore&lt;/code&gt;. The dialect is &lt;code&gt;store.QueryLanguage&lt;/code&gt;. Check it before you send a command.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Engine&lt;/th&gt;
&lt;th&gt;&lt;code&gt;QueryLanguage&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SQLite, SQLCipher&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Sql&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DuckDB, Firebird&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Sql&lt;/code&gt; when native. &lt;code&gt;None&lt;/code&gt; on the JSON fallback&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nuvexa&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Nql&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LiteDB, Realm, LMDB, RocksDB, LevelDB&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;None&lt;/code&gt; — the call throws &lt;code&gt;LocalStoreException&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;QueryLanguage&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;StoreQueryLanguage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sql&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"UPDATE users SET Status = ? WHERE City = ?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"active"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"London"&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;adults&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;QueryAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"SELECT * FROM users WHERE Age &amp;gt;= ?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;21&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;QueryLanguage&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;StoreQueryLanguage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Nql&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;adults&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;QueryAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"""db.users.find({ age: { $gte: 21 } }).sort({ name: 1 }).limit(20)"""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SQL placeholders are &lt;code&gt;?&lt;/code&gt;. NQL is the text NuvexaDB already speaks. &lt;code&gt;INuvexaLocalStore.ExecuteNqlAsync&lt;/code&gt; still returns raw JSON strings. Prefer &lt;code&gt;QueryAsync&amp;lt;T&amp;gt;&lt;/code&gt; when you want &lt;code&gt;Person&lt;/code&gt; instances. NQL update and delete need NuvexaDB 1.0.2 or newer. LocalStore 1.1 references &lt;code&gt;Nuventra.NuvexaDB&lt;/code&gt; 1.0.7 for the Nuvexa backend.&lt;/p&gt;

&lt;p&gt;The shared path for application code remains &lt;code&gt;FindAsync&lt;/code&gt;. Reach for &lt;code&gt;QueryAsync&lt;/code&gt; when a screen needs SQL or NQL that &lt;code&gt;StoreFilter&lt;/code&gt; does not express, and when you have already chosen an engine whose &lt;code&gt;QueryLanguage&lt;/code&gt; is &lt;code&gt;Sql&lt;/code&gt; or &lt;code&gt;Nql&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A generated DAO
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;[StoreDao]&lt;/code&gt; marks an interface. The generator emits an implementation that wraps &lt;code&gt;IStoreCollection&amp;lt;T&amp;gt;&lt;/code&gt; and &lt;code&gt;QueryAsync&lt;/code&gt;. CRUD method names map to the collection. &lt;code&gt;[StoreRaw]&lt;/code&gt; calls &lt;code&gt;QueryAsync&lt;/code&gt;. Placeholders are &lt;code&gt;{parameterName}&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;StoreDao&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IPersonDao&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;InsertAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Person&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;FindByIdAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;StoreRaw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;Sql&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"SELECT * FROM users WHERE Age &amp;gt;= {minAge}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Nql&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"db.users.find({ age: { $gte: {minAge} } })"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IReadOnlyList&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;FindAdultsAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;minAge&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddMauiLocalStoreDao&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IPersonDao&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;adults&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetDao&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IPersonDao&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;().&lt;/span&gt;&lt;span class="nf"&gt;FindAdultsAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;21&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;[StoreRaw]&lt;/code&gt; carries both dialects so the same interface compiles against SQLite and Nuvexa. The running store uses the string that matches &lt;code&gt;QueryLanguage&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  When something looks wrong
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What you see&lt;/th&gt;
&lt;th&gt;What to do&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;LocalStoreException&lt;/code&gt; on &lt;code&gt;ReplaceAsync&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;The POCO needs a non-empty &lt;code&gt;Id&lt;/code&gt;. Insert first, or set &lt;code&gt;Id&lt;/code&gt; before replace.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;LocalStoreException&lt;/code&gt; and the file is missing&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;CreateIfMissing&lt;/code&gt; is false. Set it true, or create the file before open.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;QueryAsync&lt;/code&gt; throws&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;QueryLanguage&lt;/code&gt; is &lt;code&gt;None&lt;/code&gt; (LiteDB, Realm, a key-value engine, or a JSON fallback). Stay on &lt;code&gt;FindAsync&lt;/code&gt;, or switch to SQLite, SQLCipher, or Nuvexa.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Open succeeded, queries feel like a folder of JSON&lt;/td&gt;
&lt;td&gt;DuckDB, Firebird, RocksDB, LevelDB, or LMDB on Catalyst is on the JSON fallback. Use SQLite, SQLCipher, NuvexaDB, LiteDB, or Realm for a real engine on every OS.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rows vanished after a backend change&lt;/td&gt;
&lt;td&gt;Dispose and &lt;code&gt;Open&lt;/code&gt; do not copy. Set &lt;code&gt;AutoMigrate&lt;/code&gt; and &lt;code&gt;Map&amp;lt;T&amp;gt;&lt;/code&gt;, or call &lt;code&gt;MigrateAsync&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Migrate skipped a collection&lt;/td&gt;
&lt;td&gt;The destination already had rows. Destination rows win.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Migrate throws with several files nearby&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;MigrateFrom&lt;/code&gt; (and &lt;code&gt;MigrateFromPath&lt;/code&gt; if the source is not beside the destination).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Encrypted &lt;code&gt;.nvx&lt;/code&gt; or SQLCipher will not open&lt;/td&gt;
&lt;td&gt;Pass the same &lt;code&gt;EncryptionKey&lt;/code&gt; used at create. Store it in &lt;code&gt;SecureStorage&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SQLCipher opened the old SQLite file&lt;/td&gt;
&lt;td&gt;Use a different path, such as &lt;code&gt;app-cipher.db&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Where LocalStore sits next to the other tools
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Need&lt;/th&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Local rows or documents, and the host picks the engine&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;LocalStore&lt;/strong&gt; — &lt;code&gt;Plugin.Maui.LocalStore&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Offline-first writes, a sync queue, and conflicts&lt;/td&gt;
&lt;td&gt;&lt;a href="https://nuvyntralabs.github.io/packages/plugin-maui-offline-sync/" rel="noopener noreferrer"&gt;Plugin.Maui.OfflineSync&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Durable jobs, retry, and a dead-letter path&lt;/td&gt;
&lt;td&gt;&lt;a href="https://nuvyntralabs.github.io/packages/plugin-maui-job-queue/" rel="noopener noreferrer"&gt;Plugin.Maui.JobQueue&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Retry a failed call&lt;/td&gt;
&lt;td&gt;&lt;a href="https://nuvyntralabs.github.io/packages/plugin-maui-retry-queue/" rel="noopener noreferrer"&gt;Plugin.Maui.RetryQueue&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Encrypted files (images, PDFs)&lt;/td&gt;
&lt;td&gt;&lt;a href="https://nuvyntralabs.github.io/packages/plugin-maui-file-vault/" rel="noopener noreferrer"&gt;Plugin.Maui.FileVault&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The &lt;code&gt;.nvx&lt;/code&gt; engine, NQL, and Data Studio&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://nuvyntralabs.github.io/nuvexadb/" rel="noopener noreferrer"&gt;NuvexaDB&lt;/a&gt; — &lt;code&gt;Nuventra.NuvexaDB&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A new MAUI host; LocalStore only when the spec asks&lt;/td&gt;
&lt;td&gt;&lt;a href="https://nuvyntralabs.github.io/toolkits/nuvyn/" rel="noopener noreferrer"&gt;Nuvyn&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Use the engine package directly when the app is committed to one database and needs that vendor’s query language, relationships, or tooling. Use LocalStore when the host must pick SQLite today and keep insert, find, replace, and delete stable if the file later becomes Nuvexa, Realm, or LiteDB.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package Plugin.Maui.LocalStore
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Register &lt;code&gt;UseMauiLocalStore&lt;/code&gt; with &lt;code&gt;StoreBackend.Sqlite&lt;/code&gt; and &lt;code&gt;app.db&lt;/code&gt;, insert one &lt;code&gt;Person&lt;/code&gt;, and &lt;code&gt;FindByIdAsync&lt;/code&gt; it. To see every engine, clone the sample. &lt;code&gt;MauiProgram&lt;/code&gt; there leaves registration to a backend picker that calls &lt;code&gt;LocalStore.Open&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;NuGet: &lt;a href="https://www.nuget.org/packages/Plugin.Maui.LocalStore" rel="noopener noreferrer"&gt;Plugin.Maui.LocalStore&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Repository: &lt;a href="https://github.com/nuvyntralabs/Plugin.Maui.LocalStore" rel="noopener noreferrer"&gt;github.com/nuvyntralabs/Plugin.Maui.LocalStore&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Sample: &lt;a href="https://github.com/nuvyntralabs/Plugin.Maui.LocalStore/tree/main/samples/Plugin.Maui.LocalStore.Sample" rel="noopener noreferrer"&gt;Plugin.Maui.LocalStore.Sample&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Get started: &lt;a href="https://nuvyntralabs.github.io/packages/plugin-maui-local-store/integration/" rel="noopener noreferrer"&gt;nuvyntralabs.github.io/packages/plugin-maui-local-store/integration/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Comparison: &lt;a href="https://nuvyntralabs.github.io/packages/plugin-maui-local-store/comparison/" rel="noopener noreferrer"&gt;nuvyntralabs.github.io/packages/plugin-maui-local-store/comparison/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;By &lt;a href="https://www.linkedin.com/in/niladri-padhy-7ab41626/" rel="noopener noreferrer"&gt;Niladri&lt;/a&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>maui</category>
      <category>database</category>
      <category>opensource</category>
    </item>
    <item>
      <title>NuvexaDB 1.0.7: One Embedded .nvx File for .NET, MAUI, and Every Other Host</title>
      <dc:creator>NuvyntraLabs</dc:creator>
      <pubDate>Wed, 23 Sep 2026 09:01:24 +0000</pubDate>
      <link>https://dev.to/niladri_prasadpadhy_ccee/nuvexadb-107-one-embedded-nvx-file-for-net-maui-and-every-other-host-2foc</link>
      <guid>https://dev.to/niladri_prasadpadhy_ccee/nuvexadb-107-one-embedded-nvx-file-for-net-maui-and-every-other-host-2foc</guid>
      <description>&lt;p&gt;A mobile app, a Kotlin service, and a desktop workbench can share one database file when they all run the same engine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NuvexaDB&lt;/strong&gt; is that engine. Version &lt;strong&gt;1.0.7&lt;/strong&gt; stores BSON documents in a single portable &lt;strong&gt;&lt;code&gt;.nvx&lt;/code&gt;&lt;/strong&gt; file, queries them with &lt;strong&gt;NQL&lt;/strong&gt; (Nuvexa Query Language), and can seal the file with &lt;strong&gt;AES-256-GCM&lt;/strong&gt;. It runs in-process. .NET and .NET MAUI call the managed library. Java, Kotlin, Swift, Flutter, React Native, Python, Node.js, Go, and C++ load a Native AOT build of the same library through &lt;code&gt;nuvexa.h&lt;/code&gt; (ABI v2).&lt;/p&gt;

&lt;p&gt;A file written from Kotlin is the file Nuvexa Data Studio, a MAUI app, and a Swift host open.&lt;/p&gt;

&lt;p&gt;The package id is &lt;code&gt;Nuventra.NuvexaDB&lt;/code&gt;. The MauiEssentials catalog is published under &lt;strong&gt;Nuvyntra&lt;/strong&gt; Labs. Both spellings are intentional.&lt;/p&gt;

&lt;h2&gt;
  
  
  What NuvexaDB is
&lt;/h2&gt;

&lt;p&gt;NuvexaDB is a standalone embedded NoSQL database. MIT licensed. One process holds an exclusive lock on a path. Concurrent reads on that open handle are allowed. Writes stay exclusive.&lt;/p&gt;

&lt;p&gt;It does two jobs for the host:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Nuventra.NuvexaDB&lt;/code&gt;&lt;/strong&gt; is the engine: pages, WAL, B+tree indexes, NQL, and optional encryption. A .NET app references this package and talks to &lt;code&gt;NuvexaDatabase&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Nuventra.NuvexaDB.Native&lt;/code&gt;&lt;/strong&gt; is the same engine compiled to a shared library. Language SDKs pass UTF-8 JSON in and JSON out. They leave page layout inside the engine.&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Engine&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://github.com/nuvyntralabs/NuvexaDB" rel="noopener noreferrer"&gt;&lt;code&gt;Nuventra.NuvexaDB&lt;/code&gt;&lt;/a&gt; 1.0.7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CLI&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Nuventra.NuvexaDB.Cli&lt;/code&gt; — command &lt;code&gt;nuvexa&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Release&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/nuvyntralabs/NuvexaDB/releases/tag/v1.0.7" rel="noopener noreferrer"&gt;v1.0.7&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Source&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/nuvyntralabs/NuvexaDB" rel="noopener noreferrer"&gt;github.com/nuvyntralabs/NuvexaDB&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docs&lt;/td&gt;
&lt;td&gt;&lt;a href="https://nuvyntralabs.github.io/nuvexadb/" rel="noopener noreferrer"&gt;nuvyntralabs.github.io/nuvexadb/&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Guides&lt;/td&gt;
&lt;td&gt;&lt;a href="https://nuvyntralabs.github.io/nuvexadb/integration/" rel="noopener noreferrer"&gt;Platform integration&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;1.0.7 installs from the GitHub Release zip&lt;/strong&gt; that matches your language and CPU. nuget.org and the language package registries are not the install path for this version. Download one zip (plus the native library when the guide says so). Leave the rest of the release page alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why one file
&lt;/h2&gt;

&lt;p&gt;An embedded store keeps the database next to the app: no server process, no connection string, no second deployment. NuvexaDB adds a written file format so that choice survives a language change.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Piece&lt;/th&gt;
&lt;th&gt;What it gives you&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.nvx&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;One application file. Creates and writes use format 2. Format 1 still opens.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Collections&lt;/td&gt;
&lt;td&gt;Named sets of documents. &lt;code&gt;_id&lt;/code&gt; is the primary key.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NQL&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;find&lt;/code&gt;, &lt;code&gt;aggregate&lt;/code&gt;, &lt;code&gt;update&lt;/code&gt;, and &lt;code&gt;delete&lt;/code&gt; as text, on every host.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Indexes&lt;/td&gt;
&lt;td&gt;B+tree. Compound indexes. Format 2 numeric keys (&lt;code&gt;d:&lt;/code&gt;) keep range scans in order.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WAL&lt;/td&gt;
&lt;td&gt;Crash recovery. A committed insert survives a hard stop. Sibling file: &lt;code&gt;&amp;lt;path&amp;gt;.nvx-wal&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Encryption&lt;/td&gt;
&lt;td&gt;Optional. Argon2id wraps a random data key. AES-256-GCM seals each page.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data Studio&lt;/td&gt;
&lt;td&gt;Desktop workbench for the same file on Windows, macOS, and Linux.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Documents on new pages are BSON. Public APIs stay JSON: &lt;code&gt;NuvexaDocument.Parse&lt;/code&gt;, NQL, Data Studio, and the C ABI. A payload that still looks like &lt;code&gt;{…}&lt;/code&gt; reads as legacy UTF-8 JSON, so mixed files stay valid.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you need
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Host&lt;/th&gt;
&lt;th&gt;What to download from &lt;a href="https://github.com/nuvyntralabs/NuvexaDB/releases/tag/v1.0.7" rel="noopener noreferrer"&gt;v1.0.7&lt;/a&gt;
&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;.NET / .NET MAUI, WPF, WinUI, Avalonia, Uno&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;NuvexaDB-NuGet.zip&lt;/code&gt; — the &lt;code&gt;Nuventra.NuvexaDB&lt;/code&gt; nupkg. No native library.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CLI&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;NuvexaDB-Cli.zip&lt;/code&gt; — install as a global tool. Keep it out of the app's &lt;code&gt;PackageReference&lt;/code&gt; list.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Java / Kotlin desktop&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;NuvexaDB-Java-&amp;lt;rid&amp;gt;.zip&lt;/code&gt; and &lt;code&gt;NuvexaDB-Native-&amp;lt;rid&amp;gt;.zip&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Android&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;NuvexaDB-Android.zip&lt;/code&gt; (&lt;code&gt;libnuvexa.so&lt;/code&gt; for arm64-v8a)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Swift&lt;/td&gt;
&lt;td&gt;Swift zip plus the macOS dylib, or &lt;code&gt;NuvexaDB-Native-iOS.zip&lt;/code&gt; (&lt;code&gt;Nuvexa.xcframework&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flutter, React Native, Python, Node.js, Go, C++&lt;/td&gt;
&lt;td&gt;The language zip &lt;strong&gt;and&lt;/strong&gt; the native zip for that OS and CPU&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;rid&amp;gt;&lt;/code&gt; is the runtime id in the file name: &lt;code&gt;osx-arm64&lt;/code&gt;, &lt;code&gt;osx-x64&lt;/code&gt;, &lt;code&gt;win-x64&lt;/code&gt;, &lt;code&gt;win-arm64&lt;/code&gt;, &lt;code&gt;linux-x64&lt;/code&gt;, &lt;code&gt;linux-arm64&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Native library names inside the zip:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Machine&lt;/th&gt;
&lt;th&gt;Library&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;macOS&lt;/td&gt;
&lt;td&gt;&lt;code&gt;libnuvexa.dylib&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Windows&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nuvexa.dll&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Linux and Android&lt;/td&gt;
&lt;td&gt;&lt;code&gt;libnuvexa.so&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;iOS device and simulator&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Nuvexa.xcframework&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Python, Node, Flutter desktop, and the JVM fallback read &lt;code&gt;NUVEXA_NATIVE_LIB&lt;/code&gt; (full path of the library). Swift, Go, and C++ read &lt;code&gt;NUVEXA_NATIVE_DIR&lt;/code&gt; (the folder that contains it).&lt;/p&gt;

&lt;h2&gt;
  
  
  Create a database from .NET
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet new console &lt;span class="nt"&gt;-n&lt;/span&gt; Acme.Store &lt;span class="nt"&gt;-f&lt;/span&gt; net10.0
&lt;span class="nb"&gt;cd &lt;/span&gt;Acme.Store
dotnet add package Nuventra.NuvexaDB &lt;span class="nt"&gt;--source&lt;/span&gt; /path/to/unzipped-nuget
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;/path/to/unzipped-nuget&lt;/code&gt; is the folder that contains &lt;code&gt;Nuventra.NuvexaDB.1.0.7.nupkg&lt;/code&gt; after you extract &lt;code&gt;NuvexaDB-NuGet.zip&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Nuventra.NuvexaDB&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;NuvexaDatabase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"app.nvx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;NuvexaCreateOptions&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;EncryptionKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"correct-horse"&lt;/span&gt; &lt;span class="c1"&gt;// omit or null for a plaintext file&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// db.FormatVersion == 2&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetCollection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;InsertAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NuvexaDocument&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"""{"&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="s"&gt;":"&lt;/span&gt;&lt;span class="n"&gt;Ada&lt;/span&gt;&lt;span class="s"&gt;","&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="s"&gt;":36}"""&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EnsureIndexAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"age"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EnsureIndexAsync&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s"&gt;"city"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"""db.users.find({ age: { $gte: 21 } }).sort({ name: 1 }).limit(20)"""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;GetCollection&lt;/code&gt; creates the collection when it is missing. &lt;code&gt;await using&lt;/code&gt; closes the file. One process per path.&lt;/p&gt;

&lt;p&gt;On MAUI, put the file under &lt;code&gt;FileSystem.AppDataDirectory&lt;/code&gt; so the iOS and Android sandbox rules apply. Open it the same way on a later launch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NuvexaDatabase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsEncrypted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsNullOrEmpty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;InvalidOperationException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"This .nvx is encrypted. Supply EncryptionKey."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;opened&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;NuvexaDatabase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;NuvexaOpenOptions&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;EncryptionKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is no &lt;code&gt;DropDatabase&lt;/code&gt; API. Dispose the handle, then delete the &lt;code&gt;.nvx&lt;/code&gt; and its &lt;code&gt;-wal&lt;/code&gt; sibling.&lt;/p&gt;

&lt;p&gt;Typed LINQ stays on .NET:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;adults&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetCollection&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"people"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Age&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="m"&gt;21&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The same file from another language
&lt;/h2&gt;

&lt;p&gt;Other SDKs call &lt;code&gt;nuvexa_create&lt;/code&gt;, &lt;code&gt;nuvexa_insert&lt;/code&gt;, and &lt;code&gt;nuvexa_execute&lt;/code&gt;. Status codes cross the boundary (&lt;code&gt;0&lt;/code&gt; ok, &lt;code&gt;2&lt;/code&gt; encryption, &lt;code&gt;3&lt;/code&gt; integrity). The SDK frees every returned string with &lt;code&gt;nuvexa_free&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Kotlin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nc"&gt;NuvexaDatabase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"app.nvx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"correct-horse"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"""{"name":"Ada","age":36}"""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"db.users.find({ age: { \$gte: 21 } }).limit(20)"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Swift:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="kt"&gt;NuvexaDatabase&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"app.nvx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"correct-horse"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="s"&gt;"{"&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="s"&gt;":"&lt;/span&gt;&lt;span class="kt"&gt;Ada&lt;/span&gt;&lt;span class="s"&gt;","&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="s"&gt;":36}"&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"db.users.find({ age: { $gte: 21 } }).limit(20)"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each host guide on the &lt;a href="https://nuvyntralabs.github.io/nuvexadb/integration/" rel="noopener noreferrer"&gt;integration page&lt;/a&gt; covers the empty project, the zip to unzip, create / open / close, collections, documents, and the encryption password.&lt;/p&gt;

&lt;h2&gt;
  
  
  NQL
&lt;/h2&gt;

&lt;p&gt;NQL is the query text for Data Studio, &lt;code&gt;ExecuteAsync&lt;/code&gt;, &lt;code&gt;nuvexa query&lt;/code&gt;, and &lt;code&gt;nuvexa_execute&lt;/code&gt;. Unquoted keys are accepted.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;address.city&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Bengaluru&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;$gte&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;21&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;project&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tickets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;({}).&lt;/span&gt;&lt;span class="nf"&gt;page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Family&lt;/th&gt;
&lt;th&gt;Operators&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Find&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;$eq&lt;/code&gt; &lt;code&gt;$ne&lt;/code&gt; &lt;code&gt;$gt&lt;/code&gt; &lt;code&gt;$gte&lt;/code&gt; &lt;code&gt;$lt&lt;/code&gt; &lt;code&gt;$lte&lt;/code&gt; &lt;code&gt;$in&lt;/code&gt; &lt;code&gt;$nin&lt;/code&gt; &lt;code&gt;$and&lt;/code&gt; &lt;code&gt;$or&lt;/code&gt; &lt;code&gt;$exists&lt;/code&gt; &lt;code&gt;$regex&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Update&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;$set&lt;/code&gt; &lt;code&gt;$unset&lt;/code&gt; &lt;code&gt;$inc&lt;/code&gt; &lt;code&gt;$push&lt;/code&gt; &lt;code&gt;$pull&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Aggregate&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;$match&lt;/code&gt; &lt;code&gt;$project&lt;/code&gt; &lt;code&gt;$sort&lt;/code&gt; &lt;code&gt;$skip&lt;/code&gt; &lt;code&gt;$limit&lt;/code&gt; &lt;code&gt;$count&lt;/code&gt; &lt;code&gt;$group&lt;/code&gt; &lt;code&gt;$lookup&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;explain()&lt;/code&gt; reports &lt;code&gt;ID&lt;/code&gt;, &lt;code&gt;IXSCAN&lt;/code&gt;, &lt;code&gt;COLLSCAN&lt;/code&gt;, &lt;code&gt;AGGREGATE&lt;/code&gt;, &lt;code&gt;UPDATE&lt;/code&gt;, or &lt;code&gt;DELETE&lt;/code&gt;. Equality on an index prefix can &lt;code&gt;IXSCAN&lt;/code&gt;. &lt;code&gt;$lookup&lt;/code&gt; refuses a foreign collection larger than &lt;code&gt;LookupMaxDocuments&lt;/code&gt; (default 100 000; &lt;code&gt;0&lt;/code&gt; disables the cap).&lt;/p&gt;

&lt;p&gt;&lt;code&gt;db.&amp;lt;collection&amp;gt;.update&lt;/code&gt; and &lt;code&gt;delete&lt;/code&gt; need engine &lt;strong&gt;1.0.2 or later&lt;/strong&gt;. GridFS-style bytes go through &lt;code&gt;db.Files.UploadAsync&lt;/code&gt; / &lt;code&gt;DownloadAsync&lt;/code&gt; (&lt;code&gt;fs.files&lt;/code&gt; / &lt;code&gt;fs.chunks&lt;/code&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Encryption
&lt;/h2&gt;

&lt;p&gt;Pass a non-empty &lt;code&gt;EncryptionKey&lt;/code&gt; at create time and on every later open. The passphrase is UTF-8. It is never written into the file.&lt;/p&gt;

&lt;p&gt;The engine derives a key-encryption key with Argon2id (app &lt;code&gt;Create()&lt;/code&gt; defaults to 16 MiB, which is the mobile setting; Data Studio and the CLI use 64 MiB). A random 32-byte data key wraps the page cipher. A verifier fails a wrong passphrase before any data page is touched. &lt;code&gt;ChangeEncryptionKeyAsync&lt;/code&gt; re-wraps that data key. Pages stay as they are.&lt;/p&gt;

&lt;p&gt;Opening an encrypted file without the key throws &lt;code&gt;NuvexaEncryptionException&lt;/code&gt;. A tampered or corrupt file throws &lt;code&gt;NuvexaIntegrityException&lt;/code&gt; and stays closed. The engine does not repair it.&lt;/p&gt;

&lt;p&gt;Store the secret in the platform keystore: &lt;code&gt;SecureStorage&lt;/code&gt; on MAUI, Credential Manager on Windows, Keychain on Apple, Android Keystore on Android. A local demo can use a throwaway string. A shipped app keeps that string out of source, &lt;code&gt;appsettings.json&lt;/code&gt;, logs, and crash reports.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data Studio, the CLI, and the editors
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Nuvexa Data Studio&lt;/strong&gt; (&lt;code&gt;Nuventra.NuvexaDB.Explorer&lt;/code&gt;) is an Avalonia workbench for one &lt;code&gt;.nvx&lt;/code&gt;. Windows (x64 and ARM64 &lt;code&gt;.msi&lt;/code&gt;), macOS (unsigned &lt;code&gt;.pkg&lt;/code&gt; into &lt;code&gt;/Applications&lt;/code&gt;), and Linux (&lt;code&gt;.deb&lt;/code&gt; / &lt;code&gt;.rpm&lt;/code&gt; for x64 and ARM64). Three tabs: Database Structure, Browse Data (200-row pages), and NQL. Import and export JSON or CSV. Compact, backup, restore, and change the encryption key from the Tools menu. Drag a &lt;code&gt;.nvx&lt;/code&gt; onto the window. An encrypted file asks for the key. Recent files remember the last 12 paths and never the passphrase.&lt;/p&gt;

&lt;p&gt;The same session backs the Visual Studio tool window and the VS Code / Cursor custom editor. Those VSIX packages bundle &lt;code&gt;nuvexa&lt;/code&gt; under &lt;code&gt;cli/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The standalone CLI is a global dotnet tool. Extract &lt;code&gt;NuvexaDB-Cli.zip&lt;/code&gt; and install from that folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet tool &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; Nuventra.NuvexaDB.Cli &lt;span class="nt"&gt;--add-source&lt;/span&gt; /path/to/NuvexaDB-Cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;nuvexa&lt;/code&gt; covers query, browse, explain, samples, backup, and restore. Do not &lt;code&gt;dotnet add package&lt;/code&gt; the CLI into an application.&lt;/p&gt;

&lt;h2&gt;
  
  
  When something looks wrong
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What you see&lt;/th&gt;
&lt;th&gt;What to do&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;NuvexaEncryptionException&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The file is encrypted. Pass the same &lt;code&gt;EncryptionKey&lt;/code&gt; you used at create. Data Studio prompts; a library call does not.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;NuvexaIntegrityException&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Open stopped on CRC, GCM, or HMAC. Leave the file closed. Restore from backup.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Second open throws&lt;/td&gt;
&lt;td&gt;Another process already holds that path. Close Data Studio, the CLI, or the other app first.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Python or Node cannot create&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;NUVEXA_NATIVE_LIB&lt;/code&gt; to the full path of &lt;code&gt;libnuvexa&lt;/code&gt; / &lt;code&gt;nuvexa.dll&lt;/code&gt; from the matching native zip.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Package not on nuget.org&lt;/td&gt;
&lt;td&gt;Expected for 1.0.7. &lt;code&gt;dotnet add package&lt;/code&gt; with &lt;code&gt;--source&lt;/code&gt; pointed at the unzipped &lt;code&gt;NuvexaDB-NuGet&lt;/code&gt; folder.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Old file still opens&lt;/td&gt;
&lt;td&gt;Format 1 stays readable. The next write promotes it to format 2.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;$lookup&lt;/code&gt; refuses a collection&lt;/td&gt;
&lt;td&gt;The foreign side is over &lt;code&gt;LookupMaxDocuments&lt;/code&gt;. Raise the cap, or set it to &lt;code&gt;0&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Where NuvexaDB sits next to the other tools
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Need&lt;/th&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Embedded NoSQL, NQL, encryption, Data Studio, many languages&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;NuvexaDB&lt;/strong&gt; — &lt;code&gt;Nuventra.NuvexaDB&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MAUI CRUD that can sit on NuvexaDB or SQLite&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://nuvyntralabs.github.io/packages/plugin-maui-local-store/" rel="noopener noreferrer"&gt;Plugin.Maui.LocalStore&lt;/a&gt;. &lt;code&gt;QueryAsync&lt;/code&gt; can run NQL when the backend is Nuvexa.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A new MAUI host, with NuvexaDB added when the spec asks&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://nuvyntralabs.github.io/toolkits/nuvyn/" rel="noopener noreferrer"&gt;Nuvyn&lt;/a&gt;. &lt;code&gt;/nuvyn.plan&lt;/code&gt; picks it. It is not in the default &lt;code&gt;init&lt;/code&gt; set.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Browse or query a file you already have&lt;/td&gt;
&lt;td&gt;Data Studio, or &lt;code&gt;nuvexa&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# After extracting NuvexaDB-NuGet.zip from the v1.0.7 release:&lt;/span&gt;
dotnet new console &lt;span class="nt"&gt;-n&lt;/span&gt; Acme.Store &lt;span class="nt"&gt;-f&lt;/span&gt; net10.0
&lt;span class="nb"&gt;cd &lt;/span&gt;Acme.Store
dotnet add package Nuventra.NuvexaDB &lt;span class="nt"&gt;--source&lt;/span&gt; /path/to/unzipped-nuget
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create &lt;code&gt;app.nvx&lt;/code&gt;, insert one document, and run the &lt;code&gt;find&lt;/code&gt; above. Open that file in Data Studio and run the same NQL in the NQL tab.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Release: &lt;a href="https://github.com/nuvyntralabs/NuvexaDB/releases/tag/v1.0.7" rel="noopener noreferrer"&gt;github.com/nuvyntralabs/NuvexaDB/releases/tag/v1.0.7&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Repository: &lt;a href="https://github.com/nuvyntralabs/NuvexaDB" rel="noopener noreferrer"&gt;github.com/nuvyntralabs/NuvexaDB&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Product page: &lt;a href="https://nuvyntralabs.github.io/nuvexadb/" rel="noopener noreferrer"&gt;nuvyntralabs.github.io/nuvexadb/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Integration guides: &lt;a href="https://nuvyntralabs.github.io/nuvexadb/integration/" rel="noopener noreferrer"&gt;nuvyntralabs.github.io/nuvexadb/integration/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;NQL: &lt;a href="https://nuvyntralabs.github.io/nuvexadb/docs/query/" rel="noopener noreferrer"&gt;nuvyntralabs.github.io/nuvexadb/docs/query/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;By &lt;a href="https://www.linkedin.com/in/niladri-padhy-7ab41626/" rel="noopener noreferrer"&gt;Niladri&lt;/a&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>nosql</category>
      <category>database</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Nuvyn CLI 1.2.0: Spec-Driven .NET MAUI, From a New App or One You Already Have</title>
      <dc:creator>NuvyntraLabs</dc:creator>
      <pubDate>Wed, 23 Sep 2026 08:37:49 +0000</pubDate>
      <link>https://dev.to/niladri_prasadpadhy_ccee/nuvyn-cli-120-spec-driven-net-maui-from-a-new-app-or-one-you-already-have-50nd</link>
      <guid>https://dev.to/niladri_prasadpadhy_ccee/nuvyn-cli-120-spec-driven-net-maui-from-a-new-app-or-one-you-already-have-50nd</guid>
      <description>&lt;p&gt;A coding agent will happily build a .NET MAUI app from a chat thread. The next session often builds a different one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nuvyn&lt;/strong&gt; is the CLI that keeps that work on one stack and one written spec. Version &lt;strong&gt;1.2.0&lt;/strong&gt; is on nuget.org as the global tool &lt;code&gt;NuvyntraLabs.Nuvyn.Cli&lt;/code&gt;. The command is &lt;code&gt;nuvyn&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This post is the walkthrough: what the tool is, how to install it, how to start a new app, how to attach it to an app you already have, and the slash commands the agent runs after that.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Nuvyn is
&lt;/h2&gt;

&lt;p&gt;Nuvyn is a standalone &lt;code&gt;PackAsTool&lt;/code&gt; CLI for &lt;strong&gt;.NET 10&lt;/strong&gt;. It depends on &lt;code&gt;System.CommandLine&lt;/code&gt; and &lt;code&gt;Spectre.Console&lt;/code&gt;. It is MIT licensed.&lt;/p&gt;

&lt;p&gt;It does two jobs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;nuvyn init&lt;/code&gt;&lt;/strong&gt; creates a new four-platform MAUI host (Android, iOS, Mac Catalyst, Windows) and locks plan and implement to the Nuvyntra stack.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;nuvyn adopt&lt;/code&gt;&lt;/strong&gt; attaches the same slash chain to an existing MAUI app. It writes workflow files only. The host you already have stays as it is.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The product domain is yours: clinic, field, bank, civic, retail, or anything else. The skills stay domain-agnostic. On a new host they lock only the MAUI and Lumina stack.&lt;/p&gt;

&lt;p&gt;Individual plugins and &lt;code&gt;NuvyntraLabs.UIKit&lt;/code&gt; stay installable on their own. You do not need Nuvyn to use one package.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Package&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://www.nuget.org/packages/NuvyntraLabs.Nuvyn.Cli" rel="noopener noreferrer"&gt;&lt;code&gt;NuvyntraLabs.Nuvyn.Cli&lt;/code&gt;&lt;/a&gt; 1.2.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Command&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nuvyn&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Source&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/nuvyntralabs/Nuvyn" rel="noopener noreferrer"&gt;github.com/nuvyntralabs/Nuvyn&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docs&lt;/td&gt;
&lt;td&gt;&lt;a href="https://nuvyntralabs.github.io/toolkits/nuvyn/" rel="noopener noreferrer"&gt;nuvyntralabs.github.io/toolkits/nuvyn/&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Guide&lt;/td&gt;
&lt;td&gt;&lt;a href="https://nuvyntralabs.github.io/toolkits/nuvyn/guide/" rel="noopener noreferrer"&gt;Install, init, adopt, slash workflow&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Install it as a &lt;strong&gt;global dotnet tool&lt;/strong&gt;. Do not add &lt;code&gt;NuvyntraLabs.Nuvyn.Cli&lt;/code&gt; as a &lt;code&gt;PackageReference&lt;/code&gt; in an app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a spec, when the agent can just code
&lt;/h2&gt;

&lt;p&gt;Spec-driven development treats a checked-in specification as the source of truth. Code follows that spec. A chat thread is a prompt log: it disappears, it contradicts itself, and a teammate cannot review it.&lt;/p&gt;

&lt;p&gt;Nuvyn writes the artifacts into the repo:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Artifact&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Constitution&lt;/td&gt;
&lt;td&gt;Standing principles: stack, platforms, privacy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Specification&lt;/td&gt;
&lt;td&gt;This increment: users, journeys, edge cases&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Plan&lt;/td&gt;
&lt;td&gt;Smallest package set and one screen recipe per screen&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tasks&lt;/td&gt;
&lt;td&gt;Dependency-ordered work (&lt;code&gt;T001 [P] [US1]&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Analysis&lt;/td&gt;
&lt;td&gt;Consistency check before production code&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That matters more once an agent writes the code. The same prompt can pick a different MVVM library on a different day. Long chats drop acceptance rules that were never written down. Agents add a login page, a second UI kit, or persistence nobody asked for, because a finished-looking app is an easy completion.&lt;/p&gt;

&lt;p&gt;Nuvyn narrows that search space. Constitution locks the stack. Specify writes the product. Plan names packages and screens. Analysis fails the loop before implement invents a second architecture.&lt;/p&gt;

&lt;p&gt;GitHub Spec Kit (&lt;code&gt;specify init&lt;/code&gt;) remains the usual choice when the stack is open. Nuvyn is the door when the host should be Nuvyntra, or when an existing MAUI app should grow through the same slash chain.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you need
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Requirement&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;.NET 10 SDK&lt;/td&gt;
&lt;td&gt;The CLI is &lt;code&gt;net10.0&lt;/code&gt;. Hosts target &lt;code&gt;net10.0-android&lt;/code&gt;, &lt;code&gt;net10.0-ios&lt;/code&gt;, &lt;code&gt;net10.0-maccatalyst&lt;/code&gt;, and &lt;code&gt;net10.0-windows10.0.19041.0&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MAUI workload&lt;/td&gt;
&lt;td&gt;So the host can build and run&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A coding agent&lt;/td&gt;
&lt;td&gt;Cursor, GitHub Copilot, Claude Code, Gemini CLI, Codex, Windsurf, or any other Spec Kit agent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;nuget.org&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;init&lt;/code&gt; adds the default Nuvyntra packages at the latest stable versions&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Nuvyn targets those four platforms. It is the wrong tool for Tizen, Flutter, React Native, WPF, WinUI, Avalonia, or Uno.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet tool &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; NuvyntraLabs.Nuvyn.Cli &lt;span class="nt"&gt;--source&lt;/span&gt; https://api.nuget.org/v3/index.json
nuvyn version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;nuvyn version&lt;/code&gt; prints &lt;code&gt;NuvyntraLabs.Nuvyn.Cli 1.2.0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Already installed? Update the tool only. Existing apps keep the packages they already reference:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet tool update &lt;span class="nt"&gt;-g&lt;/span&gt; NuvyntraLabs.Nuvyn.Cli &lt;span class="nt"&gt;--source&lt;/span&gt; https://api.nuget.org/v3/index.json
nuvyn version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On an interactive terminal, &lt;code&gt;nuvyn&lt;/code&gt; asks every four hours whether to update from nuget.org (&lt;code&gt;[y/N]&lt;/code&gt;, default no). Skip that prompt with &lt;code&gt;--no-update-check&lt;/code&gt; or &lt;code&gt;NUVYNTRA_NO_UPDATE_CHECK=1&lt;/code&gt;. The cache file is &lt;code&gt;~/.nuvyntra/cli-updates.json&lt;/code&gt;, shared with &lt;code&gt;maui-dev&lt;/code&gt; and &lt;code&gt;maui-perf&lt;/code&gt;. The CLIs do not phone home.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create a new app
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;nuvyn init &amp;lt;folder_name&amp;gt;&lt;/code&gt; always creates a &lt;strong&gt;new folder&lt;/strong&gt;. If that name already exists, init prints an error and exits &lt;code&gt;1&lt;/code&gt;. The existing tree is left untouched.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nuvyn init HarborDesk &lt;span class="nt"&gt;--agent&lt;/span&gt; cursor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Omit &lt;code&gt;--agent&lt;/code&gt; and the CLI shows a searchable picker (&lt;code&gt;cursor (Cursor)&lt;/code&gt;, &lt;code&gt;agy (Antigravity)&lt;/code&gt;, and the rest of the Spec Kit set). Non-interactive runs default to Cursor.&lt;/p&gt;

&lt;p&gt;What init does:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Copies an embedded three-project host (MVVMExpress + Lumina UIKit).&lt;/li&gt;
&lt;li&gt;Adds the default Nuvyntra packages from nuget.org, unpinned, at the latest stable versions.&lt;/li&gt;
&lt;li&gt;Writes &lt;code&gt;.nuvyn/&lt;/code&gt; (constitution, templates, reference, &lt;code&gt;init-options.json&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Installs slash commands for the agent you picked.&lt;/li&gt;
&lt;li&gt;Writes a project README with the slash chain.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The tree looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HarborDesk/
├── HarborDesk.sln
├── HarborDesk/                      # MAUI app
│   ├── MauiProgram.cs
│   ├── Pages/MainPage.xaml
│   └── Resources/Images/nuvyntra.png
├── HarborDesk.Core/                 # ViewModels
├── HarborDesk.Tests/
├── .nuvyn/
│   ├── constitution.md
│   ├── init-options.json
│   ├── templates/
│   └── reference/
├── specs/                           # empty until /nuvyn.specify
├── .cursor/skills/nuvyn-*/          # when --agent cursor
└── README.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;MainPage&lt;/code&gt; is a Lumina screen: the Nuvyntra mark, an &lt;code&gt;NVHeading&lt;/code&gt; counter, and Increase / Decrease &lt;code&gt;NVButton&lt;/code&gt; controls. There is no stock &lt;code&gt;Entry&lt;/code&gt; / &lt;code&gt;Button&lt;/code&gt; / &lt;code&gt;Label&lt;/code&gt; starter, and no seeded Login / Items / Edit pages. Screens come from the spec.&lt;/p&gt;

&lt;p&gt;Build it the usual way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;HarborDesk
dotnet restore
dotnet build
dotnet build HarborDesk/HarborDesk.csproj &lt;span class="nt"&gt;-f&lt;/span&gt; net10.0-android
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Default packages on a new host
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;init&lt;/code&gt; adds these, and nothing else from the catalog, until the spec asks:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Package&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://nuvyntralabs.github.io/packages/plugin-maui-mvvmexpress/" rel="noopener noreferrer"&gt;Plugin.Maui.MVVMExpress&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;App shell, ViewModels, navigation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://nuvyntralabs.github.io/uikit/" rel="noopener noreferrer"&gt;NuvyntraLabs.UIKit&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Lumina &lt;code&gt;NV*&lt;/code&gt; controls and page recipes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://nuvyntralabs.github.io/packages/plugin-maui-httpforge/" rel="noopener noreferrer"&gt;Plugin.Maui.HttpForge&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Typed REST client&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://nuvyntralabs.github.io/packages/plugin-maui-form-validation/" rel="noopener noreferrer"&gt;Plugin.Maui.FormValidation&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Form rules on the host&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://nuvyntralabs.github.io/packages/plugin-maui-keyboard-manager/" rel="noopener noreferrer"&gt;Plugin.Maui.KeyboardManager&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Soft keyboard hide, show, and resize&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The MAUI app gets MVVMExpress plus Dialogs and Navigation, UIKit, HttpForge, FormValidation, and KeyboardManager. Core gets &lt;code&gt;Plugin.Maui.MVVMExpress.Core&lt;/code&gt; and the source generators. Tests get &lt;code&gt;Plugin.Maui.MVVMExpress.Testing&lt;/code&gt;. Versions are never pinned: &lt;code&gt;dotnet add package&lt;/code&gt; runs without &lt;code&gt;--version&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The host wires up like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;builder&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UseMauiApp&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseMvvmExpress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseNavigationPage&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;nav&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;nav&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;MainPageViewModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MainPage&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseDialogs&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseNuvyntraUIKit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseHttpForge&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseMauiFormValidation&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseKeyboardManager&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddTransient&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;MainPageViewModel&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddTransient&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;MainPage&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Register both the page and the view-model in &lt;code&gt;MauiProgram&lt;/code&gt;. &lt;code&gt;[RegisterViewModel]&lt;/code&gt; is a map for the generator, and it is not the DI registration. Chrome is &lt;code&gt;NavigationPage&lt;/code&gt; unless the spec names Shell.&lt;/p&gt;

&lt;p&gt;LocalStore, NuvexaDB, AppLock, and the rest of the catalog wait until you ask. &lt;code&gt;/nuvyn.plan&lt;/code&gt; then picks the smallest fit. An outside library needs a &lt;strong&gt;Catalog gap&lt;/strong&gt; row in &lt;code&gt;plan.md&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adopt an existing MAUI app
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1.2.0&lt;/strong&gt; adds &lt;code&gt;nuvyn adopt&lt;/code&gt;. This is the door for a tree that already exists.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;FieldApp
nuvyn adopt &lt;span class="nt"&gt;--agent&lt;/span&gt; cursor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or point at another folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nuvyn adopt &lt;span class="nt"&gt;--path&lt;/span&gt; ../FieldApp &lt;span class="nt"&gt;--agent&lt;/span&gt; copilot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adopt:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Refuses the folder when it is not a MAUI app (&lt;code&gt;UseMaui&lt;/code&gt; in a csproj), or when &lt;code&gt;.nuvyn/&lt;/code&gt; is already there.&lt;/li&gt;
&lt;li&gt;Scans MVVM, chrome, UI kit, and HTTP. The scan is read-only.&lt;/li&gt;
&lt;li&gt;Writes &lt;code&gt;.nuvyn/&lt;/code&gt;, agent skills, an empty &lt;code&gt;specs/&lt;/code&gt; folder, and &lt;code&gt;.nuvyn/adopt-report.md&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Sets &lt;code&gt;"mode": "adopt"&lt;/code&gt; in &lt;code&gt;init-options.json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Runs &lt;code&gt;maui-dev doctor&lt;/code&gt; when MauiDev is on &lt;code&gt;PATH&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It does &lt;strong&gt;not&lt;/strong&gt; add MVVMExpress, Lumina UIKit, or HttpForge. It does &lt;strong&gt;not&lt;/strong&gt; edit &lt;code&gt;MauiProgram&lt;/code&gt;, pages, or &lt;code&gt;HttpClient&lt;/code&gt; call sites. New work keeps the stack the app already uses. Lumina &lt;code&gt;NV*&lt;/code&gt; is allowed on new screens only when UIKit is already referenced. &lt;code&gt;/nuvyn.plan&lt;/code&gt; and &lt;code&gt;/nuvyn.implement&lt;/code&gt; read &lt;code&gt;adopt-report.md&lt;/code&gt; first.&lt;/p&gt;

&lt;h2&gt;
  
  
  The slash chain
&lt;/h2&gt;

&lt;p&gt;Open the &lt;strong&gt;project folder&lt;/strong&gt; in the agent you selected. Run the commands in order. Extra text after a command is the prompt. An empty &lt;code&gt;/nuvyn.specify&lt;/code&gt; asks you to describe the product.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/nuvyn.constitution → /nuvyn.specify → /nuvyn.clarify → /nuvyn.plan
    → /nuvyn.checklist → /nuvyn.task → /nuvyn.analysis
    → /nuvyn.implement → /nuvyn.converge
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Cursor the skill folders are named &lt;code&gt;nuvyn-constitution&lt;/code&gt; (a folder name cannot contain &lt;code&gt;.&lt;/code&gt;). Type &lt;code&gt;/nuvyn-constitution&lt;/code&gt; in Cursor. Type &lt;code&gt;/nuvyn.constitution&lt;/code&gt; in Copilot, Claude, Gemini, and the other command-file agents.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;You do&lt;/th&gt;
&lt;th&gt;The agent writes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/nuvyn.constitution&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Optional product rules (PII, lock, offline)&lt;/td&gt;
&lt;td&gt;Updates &lt;code&gt;.nuvyn/constitution.md&lt;/code&gt;. The stack stays locked on a new host.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/nuvyn.specify&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Required:&lt;/strong&gt; what the app is for&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;specs/NNN-short-name/spec.md&lt;/code&gt;, &lt;code&gt;.nuvyn/feature.json&lt;/code&gt;, &lt;code&gt;checklists/requirements.md&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/nuvyn.clarify&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Answer at most five questions&lt;/td&gt;
&lt;td&gt;Updates &lt;code&gt;spec.md&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/nuvyn.plan&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Extra constraints, if any&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;plan.md&lt;/code&gt; + &lt;code&gt;research.md&lt;/code&gt; — packages and one Lumina recipe per screen&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/nuvyn.checklist&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Optional quality review&lt;/td&gt;
&lt;td&gt;&lt;code&gt;checklists/&amp;lt;domain&amp;gt;.md&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/nuvyn.task&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;tasks.md&lt;/code&gt; (&lt;code&gt;T001 [P] [US1] …&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/nuvyn.analysis&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;A report only: spec, plan, and tasks agree&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/nuvyn.implement&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Optional scope, such as Foundation only&lt;/td&gt;
&lt;td&gt;Host code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/nuvyn.converge&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Appends remaining work to &lt;code&gt;tasks.md&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A specify prompt can carry the product in one line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/nuvyn.specify Resident 311 desk: sign in, report a bin miss, see live bus times.
/nuvyn.implement Implement only Foundation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Standing law lives in &lt;code&gt;.nuvyn/reference/constraints.md&lt;/code&gt;. Each slash command stays short and points at that file, so the agent spends the window on the domain instead of restating the catalog every turn.&lt;/p&gt;

&lt;p&gt;UI on a new host is Lumina first. One recipe per screen. Bound fields go inside the recipe so they replace the demo seed. Prefer &lt;code&gt;NVInputField&lt;/code&gt;, &lt;code&gt;NVPasswordField&lt;/code&gt;, and &lt;code&gt;NVButton&lt;/code&gt; when an &lt;code&gt;NV*&lt;/code&gt; control exists. Control reference: &lt;a href="https://nuvyntralabs.github.io/uikit/docs/" rel="noopener noreferrer"&gt;UIKit(MAUI) docs&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which agent gets which folder
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;nuvyn init&lt;/code&gt; and &lt;code&gt;nuvyn adopt&lt;/code&gt; write into the folder that agent already reads. The set matches Spec Kit, including more than thirty additional agents.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Agent&lt;/th&gt;
&lt;th&gt;&lt;code&gt;--agent&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;On disk&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cursor&lt;/td&gt;
&lt;td&gt;&lt;code&gt;cursor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.cursor/skills/nuvyn-*/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub Copilot&lt;/td&gt;
&lt;td&gt;&lt;code&gt;copilot&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.github/skills/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Code&lt;/td&gt;
&lt;td&gt;&lt;code&gt;claude&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.claude/commands/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemini CLI&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gemini&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.gemini/commands/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Codex CLI&lt;/td&gt;
&lt;td&gt;&lt;code&gt;codex&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.agents/skills/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Goose&lt;/td&gt;
&lt;td&gt;&lt;code&gt;goose&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.goose/recipes/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Windsurf&lt;/td&gt;
&lt;td&gt;&lt;code&gt;windsurf&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.windsurf/workflows/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Generic&lt;/td&gt;
&lt;td&gt;&lt;code&gt;generic&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.agents/commands/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Pass &lt;code&gt;--agent cursor-agent&lt;/code&gt; when that Spec Kit key is the one you already use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Refresh skills after a CLI update
&lt;/h2&gt;

&lt;p&gt;Do not re-run &lt;code&gt;nuvyn init&lt;/code&gt; on a tree that already exists. After the global tool updates, refresh slash files from inside the app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;HarborDesk
nuvyn update
nuvyn update &lt;span class="nt"&gt;--agent&lt;/span&gt; cursor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;nuvyn update&lt;/code&gt; overwrites &lt;code&gt;.nuvyn/templates/&lt;/code&gt;, &lt;code&gt;.nuvyn/reference/&lt;/code&gt;, and the agent command files. It leaves host code, &lt;code&gt;specs/&lt;/code&gt;, &lt;code&gt;.nuvyn/constitution.md&lt;/code&gt;, and &lt;code&gt;.nuvyn/adopt-report.md&lt;/code&gt; alone. It does not change &lt;code&gt;PackageReference&lt;/code&gt; versions.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;nuvyn check&lt;/code&gt; confirms &lt;code&gt;dotnet&lt;/code&gt; and the payload. Inside a greenfield app it also proves the host still uses MVVMExpress, UIKit, HttpForge, FormValidation, and KeyboardManager. Inside an adopted app it prints the inventory and skips that proof.&lt;/p&gt;

&lt;p&gt;When &lt;a href="https://nuvyntralabs.github.io/toolkits/maui-dev/" rel="noopener noreferrer"&gt;MauiDev&lt;/a&gt; is on &lt;code&gt;PATH&lt;/code&gt;, &lt;code&gt;init&lt;/code&gt;, &lt;code&gt;adopt&lt;/code&gt;, and &lt;code&gt;check&lt;/code&gt; run &lt;code&gt;maui-dev doctor --path&lt;/code&gt;. A missing doctor is a warning. Nuvyn still succeeds. Install it when you want the environment report:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet tool &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; Plugin.Maui.MauiDev.Cli &lt;span class="nt"&gt;--source&lt;/span&gt; https://api.nuget.org/v3/index.json
maui-dev doctor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  When something looks wrong
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What you see&lt;/th&gt;
&lt;th&gt;What to do&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;HarborDesk already exists&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;init&lt;/code&gt; is for a new folder. Pick another name, or delete a leftover failed scaffold and retry. For an existing MAUI app, &lt;code&gt;cd&lt;/code&gt; in and run &lt;code&gt;nuvyn adopt&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;is already a Nuvyn project&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Adopt or init already ran. Use &lt;code&gt;nuvyn update&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;is not a MAUI app&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;adopt&lt;/code&gt; needs a &lt;code&gt;UseMaui&lt;/code&gt; csproj in that folder.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Not a Nuvyn project&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;update&lt;/code&gt; and &lt;code&gt;check&lt;/code&gt; need a &lt;code&gt;.nuvyn/&lt;/code&gt; folder from &lt;code&gt;init&lt;/code&gt; or &lt;code&gt;adopt&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unable to resolve &lt;code&gt;MainPageViewModel&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Add &lt;code&gt;builder.Services.AddTransient&amp;lt;MainPageViewModel&amp;gt;()&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;AddGeneratedViewModels&lt;/code&gt; fails to build&lt;/td&gt;
&lt;td&gt;Remove that call. Register the view-model with &lt;code&gt;AddTransient&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The agent added LocalStore, Syncfusion, or Refit&lt;/td&gt;
&lt;td&gt;You did not ask. Revert. Catalog first, UIKit first.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MAUI workload or TFM errors&lt;/td&gt;
&lt;td&gt;Read the printed &lt;code&gt;maui-dev doctor&lt;/code&gt; report. Do not re-run &lt;code&gt;nuvyn init&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Update prompt every few hours&lt;/td&gt;
&lt;td&gt;Expected. Answer &lt;code&gt;n&lt;/code&gt;, or pass &lt;code&gt;--no-update-check&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Where Nuvyn sits next to the other tools
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Need&lt;/th&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;New Nuvyntra MAUI host and the spec chain&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Nuvyn&lt;/strong&gt; — &lt;code&gt;nuvyn init&lt;/code&gt;, then &lt;code&gt;nuvyn update&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Existing MAUI app, same spec chain, keep its stack&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Nuvyn&lt;/strong&gt; — &lt;code&gt;nuvyn adopt&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Diagnose an existing MAUI tree&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://nuvyntralabs.github.io/toolkits/maui-dev/" rel="noopener noreferrer"&gt;MauiDev&lt;/a&gt; — &lt;code&gt;maui-dev doctor&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sibling &lt;code&gt;.resx&lt;/code&gt; localization&lt;/td&gt;
&lt;td&gt;&lt;a href="https://nuvyntralabs.github.io/toolkits/nuvloc/" rel="noopener noreferrer"&gt;NuvLoc&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Any stack, spec only&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/github/spec-kit" rel="noopener noreferrer"&gt;GitHub Spec Kit&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;One runtime plugin&lt;/td&gt;
&lt;td&gt;The matching &lt;a href="https://nuvyntralabs.github.io/packages/" rel="noopener noreferrer"&gt;&lt;code&gt;Plugin.Maui.*&lt;/code&gt;&lt;/a&gt; package&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet tool &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; NuvyntraLabs.Nuvyn.Cli &lt;span class="nt"&gt;--source&lt;/span&gt; https://api.nuget.org/v3/index.json
nuvyn init HarborDesk &lt;span class="nt"&gt;--agent&lt;/span&gt; cursor
&lt;span class="nb"&gt;cd &lt;/span&gt;HarborDesk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open that folder in your agent and run &lt;code&gt;/nuvyn.constitution&lt;/code&gt;, then &lt;code&gt;/nuvyn.specify&lt;/code&gt; with the product in one sentence.&lt;/p&gt;

&lt;p&gt;If the app already exists:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;YourMauiApp
nuvyn adopt &lt;span class="nt"&gt;--agent&lt;/span&gt; cursor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then read &lt;code&gt;.nuvyn/adopt-report.md&lt;/code&gt; before &lt;code&gt;/nuvyn.plan&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;NuGet: &lt;a href="https://www.nuget.org/packages/NuvyntraLabs.Nuvyn.Cli" rel="noopener noreferrer"&gt;NuvyntraLabs.Nuvyn.Cli&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Repository: &lt;a href="https://github.com/nuvyntralabs/Nuvyn" rel="noopener noreferrer"&gt;github.com/nuvyntralabs/Nuvyn&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;User guide: &lt;a href="https://nuvyntralabs.github.io/toolkits/nuvyn/guide/" rel="noopener noreferrer"&gt;nuvyntralabs.github.io/toolkits/nuvyn/guide/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Technical docs: &lt;a href="https://nuvyntralabs.github.io/toolkits/nuvyn/docs/" rel="noopener noreferrer"&gt;nuvyntralabs.github.io/toolkits/nuvyn/docs/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dotnet</category>
      <category>maui</category>
      <category>cli</category>
      <category>opensource</category>
    </item>
    <item>
      <title>The Story Behind Building NuvyntraLabs</title>
      <dc:creator>NuvyntraLabs</dc:creator>
      <pubDate>Wed, 23 Sep 2026 07:45:43 +0000</pubDate>
      <link>https://dev.to/niladri_prasadpadhy_ccee/the-story-behind-building-nuvyntralabs-44ll</link>
      <guid>https://dev.to/niladri_prasadpadhy_ccee/the-story-behind-building-nuvyntralabs-44ll</guid>
      <description>&lt;p&gt;Every open-source project has a beginning.&lt;/p&gt;

&lt;p&gt;Sometimes it starts with a big idea, a business plan, or a team sitting around a whiteboard.&lt;/p&gt;

&lt;p&gt;But sometimes, it starts much more simply.&lt;/p&gt;

&lt;p&gt;A developer encounters the same problem again and again, builds a small solution for it, improves it over time, and eventually realizes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“Maybe this can help someone else too.”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That is, in many ways, the story behind &lt;strong&gt;NuvyntraLabs&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  From solving problems to building tools
&lt;/h2&gt;

&lt;p&gt;My journey with software development has always been closely connected with solving practical problems.&lt;/p&gt;

&lt;p&gt;Over the years, while working with mobile applications, .NET, Xamarin, .NET MAUI, APIs, architecture, networking, databases, background services, and many other areas, I repeatedly found myself building small utilities and reusable components.&lt;/p&gt;

&lt;p&gt;A particular problem would appear in one project.&lt;/p&gt;

&lt;p&gt;I would solve it.&lt;/p&gt;

&lt;p&gt;Then the same problem would appear in another project.&lt;/p&gt;

&lt;p&gt;So I would improve the solution and make it reusable.&lt;/p&gt;

&lt;p&gt;Eventually, many of those solutions became libraries.&lt;/p&gt;

&lt;p&gt;And some of those libraries became NuGet packages.&lt;/p&gt;

&lt;p&gt;That was one of the early foundations of what would eventually become NuvyntraLabs.&lt;/p&gt;

&lt;p&gt;The idea was not initially to build a large ecosystem.&lt;/p&gt;

&lt;p&gt;It was simply:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Build something useful. Make it reusable. Share it. Improve it.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why NuvyntraLabs?
&lt;/h2&gt;

&lt;p&gt;As the number of projects and experiments grew, I realized that keeping everything as disconnected repositories did not make much sense.&lt;/p&gt;

&lt;p&gt;There were libraries for different purposes.&lt;/p&gt;

&lt;p&gt;There were experiments.&lt;/p&gt;

&lt;p&gt;There were developer utilities.&lt;/p&gt;

&lt;p&gt;There were MAUI components.&lt;/p&gt;

&lt;p&gt;There were architectural ideas.&lt;/p&gt;

&lt;p&gt;There were CLI concepts.&lt;/p&gt;

&lt;p&gt;There were tools that could potentially become useful to other developers.&lt;/p&gt;

&lt;p&gt;I wanted a place where all of these could live under a common identity.&lt;/p&gt;

&lt;p&gt;That became &lt;strong&gt;NuvyntraLabs&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;NuvyntraLabs is not just a collection of repositories.&lt;/p&gt;

&lt;p&gt;It is an attempt to create a small developer ecosystem around ideas, tools, libraries, experimentation, and open-source collaboration.&lt;/p&gt;

&lt;h2&gt;
  
  
  The .NET MAUI connection
&lt;/h2&gt;

&lt;p&gt;One of the strongest foundations of NuvyntraLabs is the .NET MAUI ecosystem.&lt;/p&gt;

&lt;p&gt;Having worked extensively with Xamarin and later .NET MAUI, I experienced many of the challenges that developers face when building real-world cross-platform applications.&lt;/p&gt;

&lt;p&gt;Building a production application is much more than creating pages and connecting buttons.&lt;/p&gt;

&lt;p&gt;You eventually need to think about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Networking&lt;/li&gt;
&lt;li&gt;API reliability&lt;/li&gt;
&lt;li&gt;Localization&lt;/li&gt;
&lt;li&gt;Application health&lt;/li&gt;
&lt;li&gt;Updates&lt;/li&gt;
&lt;li&gt;Geolocation&lt;/li&gt;
&lt;li&gt;Storage&lt;/li&gt;
&lt;li&gt;MVVM&lt;/li&gt;
&lt;li&gt;Messaging&lt;/li&gt;
&lt;li&gt;Performance&lt;/li&gt;
&lt;li&gt;Memory management&lt;/li&gt;
&lt;li&gt;Background processing&lt;/li&gt;
&lt;li&gt;Platform-specific behavior&lt;/li&gt;
&lt;li&gt;Logging&lt;/li&gt;
&lt;li&gt;Diagnostics&lt;/li&gt;
&lt;li&gt;Architecture&lt;/li&gt;
&lt;li&gt;Developer tooling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Many of these problems are common across applications.&lt;/p&gt;

&lt;p&gt;That makes them good candidates for reusable libraries.&lt;/p&gt;

&lt;p&gt;This became one of the motivations behind building a collection of MAUI-focused tools and NuGet packages.&lt;/p&gt;

&lt;p&gt;Instead of solving the same problem separately in every application, the goal is to create reusable building blocks that developers can adopt in their own projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Open source as a learning process
&lt;/h2&gt;

&lt;p&gt;For me, open source is not only about publishing code.&lt;/p&gt;

&lt;p&gt;It is also about learning.&lt;/p&gt;

&lt;p&gt;When code exists only inside a private project, it is easy to make assumptions.&lt;/p&gt;

&lt;p&gt;When you publish it publicly, you start looking at it differently.&lt;/p&gt;

&lt;p&gt;You begin asking:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is this API understandable?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can another developer use this without knowing the original project?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is the documentation good enough?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does the package name make sense?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What happens when someone uses this differently than I expected?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Those questions make the software better.&lt;/p&gt;

&lt;p&gt;Open source creates a different kind of feedback loop.&lt;/p&gt;

&lt;p&gt;You build.&lt;/p&gt;

&lt;p&gt;You publish.&lt;/p&gt;

&lt;p&gt;You receive feedback.&lt;/p&gt;

&lt;p&gt;You discover problems.&lt;/p&gt;

&lt;p&gt;You improve.&lt;/p&gt;

&lt;p&gt;Then you publish again.&lt;/p&gt;

&lt;p&gt;That cycle is one of the most valuable parts of building NuvyntraLabs.&lt;/p&gt;

&lt;h2&gt;
  
  
  From repositories to an ecosystem
&lt;/h2&gt;

&lt;p&gt;At some point, NuvyntraLabs started becoming more than a collection of GitHub repositories.&lt;/p&gt;

&lt;p&gt;There were multiple libraries.&lt;/p&gt;

&lt;p&gt;There were NuGet packages.&lt;/p&gt;

&lt;p&gt;There were developer utilities.&lt;/p&gt;

&lt;p&gt;There were toolkits.&lt;/p&gt;

&lt;p&gt;There were ideas for CLI tools.&lt;/p&gt;

&lt;p&gt;There were experiments around AI-assisted development.&lt;/p&gt;

&lt;p&gt;And there was a growing need for proper documentation.&lt;/p&gt;

&lt;p&gt;That led to the creation of the NuvyntraLabs website and documentation ecosystem.&lt;/p&gt;

&lt;p&gt;The goal was to make the projects easier to discover.&lt;/p&gt;

&lt;p&gt;A developer should not have to search through dozens of repositories to understand what exists.&lt;/p&gt;

&lt;p&gt;Instead, the ecosystem should provide a clear entry point.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is the project?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What problem does it solve?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I install it?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I use it?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is it actively maintained?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where can I contribute?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;These questions became increasingly important.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building in public
&lt;/h2&gt;

&lt;p&gt;One of the things I want NuvyntraLabs to represent is the idea of &lt;strong&gt;building in public&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Not everything needs to be perfect before it is shared.&lt;/p&gt;

&lt;p&gt;A project can start small.&lt;/p&gt;

&lt;p&gt;A library can begin with a single feature.&lt;/p&gt;

&lt;p&gt;A CLI tool can begin as an experiment.&lt;/p&gt;

&lt;p&gt;An idea can be published, discussed, improved, or even abandoned.&lt;/p&gt;

&lt;p&gt;That is part of software development.&lt;/p&gt;

&lt;p&gt;I don't want NuvyntraLabs to be a place where everything looks artificially perfect.&lt;/p&gt;

&lt;p&gt;I want it to reflect the real process of engineering.&lt;/p&gt;

&lt;p&gt;Ideas become prototypes.&lt;/p&gt;

&lt;p&gt;Prototypes become tools.&lt;/p&gt;

&lt;p&gt;Tools become libraries.&lt;/p&gt;

&lt;p&gt;Libraries become packages.&lt;/p&gt;

&lt;p&gt;Packages become part of applications.&lt;/p&gt;

&lt;p&gt;And sometimes an experiment teaches us something valuable even if it never becomes a product.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the name NuvyntraLabs?
&lt;/h2&gt;

&lt;p&gt;The name represents the direction I wanted to take.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NuvyntraLabs&lt;/strong&gt; is intentionally broader than a single product.&lt;/p&gt;

&lt;p&gt;It is a lab.&lt;/p&gt;

&lt;p&gt;A place for experimentation.&lt;/p&gt;

&lt;p&gt;A place for engineering.&lt;/p&gt;

&lt;p&gt;A place where ideas can be tested.&lt;/p&gt;

&lt;p&gt;A place where reusable software can be created.&lt;/p&gt;

&lt;p&gt;And a place where developers can discover and contribute to projects.&lt;/p&gt;

&lt;p&gt;The “Labs” part is important.&lt;/p&gt;

&lt;p&gt;Not every project needs to become a commercial product.&lt;/p&gt;

&lt;p&gt;Some projects exist simply because they are interesting problems to solve.&lt;/p&gt;

&lt;p&gt;Some exist because developers need them.&lt;/p&gt;

&lt;p&gt;Some exist to explore a new technology.&lt;/p&gt;

&lt;p&gt;Some may eventually grow into something much larger.&lt;/p&gt;

&lt;p&gt;That freedom is important.&lt;/p&gt;

&lt;h2&gt;
  
  
  The role of AI
&lt;/h2&gt;

&lt;p&gt;The software-development landscape is changing rapidly.&lt;/p&gt;

&lt;p&gt;AI coding assistants and coding agents are becoming an increasingly important part of how developers build software.&lt;/p&gt;

&lt;p&gt;This creates another interesting challenge for open source.&lt;/p&gt;

&lt;p&gt;It is no longer enough for a library to exist somewhere on GitHub.&lt;/p&gt;

&lt;p&gt;Developers increasingly discover software through AI tools.&lt;/p&gt;

&lt;p&gt;That means libraries need to be understandable not only to humans browsing documentation, but also to tools that help developers find and use software.&lt;/p&gt;

&lt;p&gt;Clear documentation, meaningful examples, good APIs, package metadata, source code, README files, and structured information all become more important.&lt;/p&gt;

&lt;p&gt;This is an area I want NuvyntraLabs to explore further.&lt;/p&gt;

&lt;p&gt;The goal is not simply to build software for developers.&lt;/p&gt;

&lt;p&gt;It is also to make that software easier for developers — and their AI development tools — to discover and understand.&lt;/p&gt;

&lt;h2&gt;
  
  
  The community vision
&lt;/h2&gt;

&lt;p&gt;A developer ecosystem cannot be built around code alone.&lt;/p&gt;

&lt;p&gt;It needs people.&lt;/p&gt;

&lt;p&gt;That is why community is becoming an important part of NuvyntraLabs.&lt;/p&gt;

&lt;p&gt;The vision is to create a space where developers can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Discuss .NET MAUI&lt;/li&gt;
&lt;li&gt;Share solutions&lt;/li&gt;
&lt;li&gt;Explore open-source projects&lt;/li&gt;
&lt;li&gt;Exchange ideas&lt;/li&gt;
&lt;li&gt;Ask technical questions&lt;/li&gt;
&lt;li&gt;Share developer experiences&lt;/li&gt;
&lt;li&gt;Discover useful tools&lt;/li&gt;
&lt;li&gt;Contribute to projects&lt;/li&gt;
&lt;li&gt;Learn from each other&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The community should not be limited to people who already contribute code.&lt;/p&gt;

&lt;p&gt;Someone discovering their first MAUI library is part of the ecosystem.&lt;/p&gt;

&lt;p&gt;Someone reporting a bug is part of the ecosystem.&lt;/p&gt;

&lt;p&gt;Someone suggesting an improvement is part of the ecosystem.&lt;/p&gt;

&lt;p&gt;Someone writing documentation is part of the ecosystem.&lt;/p&gt;

&lt;p&gt;Someone simply sharing their experience can contribute value.&lt;/p&gt;

&lt;p&gt;Open source becomes stronger when participation is broader than just commits.&lt;/p&gt;

&lt;h2&gt;
  
  
  What NuvyntraLabs is becoming
&lt;/h2&gt;

&lt;p&gt;Today, NuvyntraLabs is still evolving.&lt;/p&gt;

&lt;p&gt;There are many ideas.&lt;/p&gt;

&lt;p&gt;Some are already implemented.&lt;/p&gt;

&lt;p&gt;Some are experiments.&lt;/p&gt;

&lt;p&gt;Some are being improved.&lt;/p&gt;

&lt;p&gt;Some may change direction.&lt;/p&gt;

&lt;p&gt;And that is okay.&lt;/p&gt;

&lt;p&gt;The objective is not to create hundreds of projects simply to have hundreds of projects.&lt;/p&gt;

&lt;p&gt;The objective is to build useful software.&lt;/p&gt;

&lt;p&gt;Over time, I want NuvyntraLabs to grow around several areas:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Developer Libraries&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Reusable libraries and NuGet packages that solve common development problems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;.NET MAUI Toolkits&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Practical components and utilities for developers building cross-platform applications.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Developer Tools&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;CLI tools and utilities that make everyday development tasks easier.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI-Assisted Development&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Exploring how AI agents can discover, understand, use, and contribute to open-source software.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Community&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A place where developers can communicate, collaborate, and learn.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Open Source&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Projects that developers can inspect, use, improve, and contribute to.&lt;/p&gt;

&lt;h2&gt;
  
  
  The journey is more important than the destination
&lt;/h2&gt;

&lt;p&gt;There is still a long way to go.&lt;/p&gt;

&lt;p&gt;The ecosystem will change.&lt;/p&gt;

&lt;p&gt;Projects will evolve.&lt;/p&gt;

&lt;p&gt;Some repositories will become more important.&lt;/p&gt;

&lt;p&gt;Some will eventually become obsolete.&lt;/p&gt;

&lt;p&gt;New technologies will appear.&lt;/p&gt;

&lt;p&gt;.NET MAUI will continue to evolve.&lt;/p&gt;

&lt;p&gt;AI will continue changing how software is built.&lt;/p&gt;

&lt;p&gt;And NuvyntraLabs will need to evolve along with them.&lt;/p&gt;

&lt;p&gt;That is actually one of the things I find exciting.&lt;/p&gt;

&lt;p&gt;NuvyntraLabs is not intended to be a finished product.&lt;/p&gt;

&lt;p&gt;It is a &lt;strong&gt;continuous engineering journey&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Every library teaches something.&lt;/p&gt;

&lt;p&gt;Every issue provides feedback.&lt;/p&gt;

&lt;p&gt;Every contribution adds perspective.&lt;/p&gt;

&lt;p&gt;Every new developer who discovers a project creates another opportunity to improve it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bigger idea
&lt;/h2&gt;

&lt;p&gt;At its heart, NuvyntraLabs is built around a simple philosophy:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create useful things. Share them openly. Learn from the community. Keep improving.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It started with individual development problems.&lt;/p&gt;

&lt;p&gt;It grew into reusable libraries.&lt;/p&gt;

&lt;p&gt;Those libraries became open-source projects.&lt;/p&gt;

&lt;p&gt;The projects became NuGet packages.&lt;/p&gt;

&lt;p&gt;The packages became a collection.&lt;/p&gt;

&lt;p&gt;The collection became an ecosystem.&lt;/p&gt;

&lt;p&gt;And now the ecosystem is becoming a community.&lt;/p&gt;

&lt;p&gt;That evolution was not planned from day one.&lt;/p&gt;

&lt;p&gt;It happened naturally through the process of building.&lt;/p&gt;

&lt;p&gt;And perhaps that is the most interesting part of the story.&lt;/p&gt;

&lt;p&gt;NuvyntraLabs is not just the story of a brand.&lt;/p&gt;

&lt;p&gt;It is the story of a developer continuing to build, experiment, learn, and share.&lt;/p&gt;

&lt;p&gt;And this is only the beginning.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Welcome to NuvyntraLabs.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Build. Share. Learn. Evolve.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;By &lt;a href="https://www.linkedin.com/in/niladri-padhy-7ab41626/" rel="noopener noreferrer"&gt;Admin&lt;/a&gt;&lt;/p&gt;

</description>
      <category>maui</category>
      <category>nuvyntralabs</category>
      <category>opensource</category>
      <category>inspiration</category>
    </item>
    <item>
      <title>.NET MAUI Ecosystem: Building More Than Just Cross-Platform Apps</title>
      <dc:creator>NuvyntraLabs</dc:creator>
      <pubDate>Wed, 23 Sep 2026 05:15:41 +0000</pubDate>
      <link>https://dev.to/niladri_prasadpadhy_ccee/net-maui-ecosystem-building-more-than-just-cross-platform-apps-h1i</link>
      <guid>https://dev.to/niladri_prasadpadhy_ccee/net-maui-ecosystem-building-more-than-just-cross-platform-apps-h1i</guid>
      <description>&lt;p&gt;The world of application development has changed dramatically over the last decade. Developers are no longer building applications for a single operating system or a single type of device. Modern applications are expected to work across multiple platforms, communicate with cloud services, operate reliably with limited connectivity, integrate with device capabilities, and provide a consistent user experience.&lt;/p&gt;

&lt;p&gt;In the .NET world, &lt;strong&gt;.NET MAUI&lt;/strong&gt; has become an important part of this cross-platform development story.&lt;/p&gt;

&lt;p&gt;But .NET MAUI is much more than a UI framework.&lt;/p&gt;

&lt;p&gt;The real strength of .NET MAUI comes from the &lt;strong&gt;ecosystem around it&lt;/strong&gt; — libraries, NuGet packages, architecture patterns, developer tools, backend technologies, cloud services, community projects, testing frameworks, and the developers who continuously build and share solutions.&lt;/p&gt;

&lt;p&gt;This is the story of the .NET MAUI ecosystem.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Is .NET MAUI?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;.NET MAUI (Multi-platform App UI)&lt;/strong&gt; is Microsoft's framework for building native applications using .NET and C# from a shared codebase.&lt;/p&gt;

&lt;p&gt;With .NET MAUI, developers can target platforms such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Android&lt;/li&gt;
&lt;li&gt;iOS&lt;/li&gt;
&lt;li&gt;macOS&lt;/li&gt;
&lt;li&gt;Windows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of maintaining completely separate application projects for every platform, developers can share a significant portion of their application code while still accessing platform-specific functionality when required.&lt;/p&gt;

&lt;p&gt;This approach makes .NET MAUI particularly interesting for teams that already have experience with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;C#&lt;/li&gt;
&lt;li&gt;.NET&lt;/li&gt;
&lt;li&gt;ASP.NET Core&lt;/li&gt;
&lt;li&gt;Azure&lt;/li&gt;
&lt;li&gt;Microsoft development tools&lt;/li&gt;
&lt;li&gt;Visual Studio&lt;/li&gt;
&lt;li&gt;NuGet&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, a framework alone does not create a successful application.&lt;/p&gt;

&lt;p&gt;That is where the ecosystem becomes important.&lt;/p&gt;




&lt;h1&gt;
  
  
  .NET MAUI Is an Ecosystem, Not Just a Framework
&lt;/h1&gt;

&lt;p&gt;When developers hear ".NET MAUI," they often think about XAML, controls, layouts and C#.&lt;/p&gt;

&lt;p&gt;Those are certainly important.&lt;/p&gt;

&lt;p&gt;But a production application usually needs much more.&lt;/p&gt;

&lt;p&gt;A typical MAUI application may require:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    .NET MAUI Application
                            │
        ┌───────────────────┼───────────────────┐
        │                   │                   │
       UI              Architecture          Platform
        │                   │                   │
   XAML / C#           MVVM / DI          Android / iOS
        │                   │                   │
        ├────────────── Libraries ───────────────┤
        │                   │                   │
     Networking         Database             Storage
        │                   │                   │
     Security          Telemetry            Logging
        │                   │                   │
        └────────────── Cloud / APIs ────────────┘
                            │
                     Developer Tools
                            │
                     CI/CD &amp;amp; Releases
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The ecosystem fills the gaps between the framework and a complete production application.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Foundation: .NET
&lt;/h1&gt;

&lt;p&gt;One of the biggest advantages of MAUI is that it sits on top of the broader .NET platform.&lt;/p&gt;

&lt;p&gt;A MAUI developer is not limited to APIs specifically created for mobile applications.&lt;/p&gt;

&lt;p&gt;They can use the enormous .NET ecosystem.&lt;/p&gt;

&lt;p&gt;This includes technologies such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;.NET libraries&lt;/li&gt;
&lt;li&gt;NuGet&lt;/li&gt;
&lt;li&gt;Dependency Injection&lt;/li&gt;
&lt;li&gt;Configuration&lt;/li&gt;
&lt;li&gt;Logging&lt;/li&gt;
&lt;li&gt;HTTP&lt;/li&gt;
&lt;li&gt;JSON serialization&lt;/li&gt;
&lt;li&gt;Cryptography&lt;/li&gt;
&lt;li&gt;LINQ&lt;/li&gt;
&lt;li&gt;Tasks and async programming&lt;/li&gt;
&lt;li&gt;Generic Host concepts&lt;/li&gt;
&lt;li&gt;ASP.NET Core&lt;/li&gt;
&lt;li&gt;Entity Framework Core&lt;/li&gt;
&lt;li&gt;Azure SDKs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This creates an important advantage.&lt;/p&gt;

&lt;p&gt;A developer can build an architecture where the mobile application and backend share common concepts, models, libraries and development practices.&lt;/p&gt;




&lt;h1&gt;
  
  
  NuGet: The Package Ecosystem
&lt;/h1&gt;

&lt;p&gt;One of the most important components of the .NET ecosystem is &lt;strong&gt;NuGet&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;NuGet allows developers to package reusable functionality and distribute it to other .NET developers.&lt;/p&gt;

&lt;p&gt;For MAUI developers, this can dramatically reduce development time.&lt;/p&gt;

&lt;p&gt;Instead of building everything from scratch, developers can use packages for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTTP networking&lt;/li&gt;
&lt;li&gt;REST APIs&lt;/li&gt;
&lt;li&gt;Authentication&lt;/li&gt;
&lt;li&gt;Logging&lt;/li&gt;
&lt;li&gt;Analytics&lt;/li&gt;
&lt;li&gt;Localization&lt;/li&gt;
&lt;li&gt;Maps&lt;/li&gt;
&lt;li&gt;Storage&lt;/li&gt;
&lt;li&gt;SQLite&lt;/li&gt;
&lt;li&gt;UI controls&lt;/li&gt;
&lt;li&gt;MVVM&lt;/li&gt;
&lt;li&gt;Dependency injection&lt;/li&gt;
&lt;li&gt;Permissions&lt;/li&gt;
&lt;li&gt;Device information&lt;/li&gt;
&lt;li&gt;Connectivity&lt;/li&gt;
&lt;li&gt;Performance monitoring&lt;/li&gt;
&lt;li&gt;Application updates&lt;/li&gt;
&lt;li&gt;Push notifications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This creates a network effect.&lt;/p&gt;

&lt;p&gt;A developer solves a problem once.&lt;/p&gt;

&lt;p&gt;They publish the solution.&lt;/p&gt;

&lt;p&gt;Another developer discovers it.&lt;/p&gt;

&lt;p&gt;The second developer provides feedback.&lt;/p&gt;

&lt;p&gt;The library improves.&lt;/p&gt;

&lt;p&gt;Eventually, the entire ecosystem benefits.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Role of Open Source
&lt;/h1&gt;

&lt;p&gt;Open source is one of the most important forces behind the MAUI ecosystem.&lt;/p&gt;

&lt;p&gt;Developers can create libraries, tools and frameworks and share them with the community.&lt;/p&gt;

&lt;p&gt;This is particularly valuable for problems that are common across applications.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"How do I handle API retries?"&lt;/p&gt;

&lt;p&gt;"How do I implement localization?"&lt;/p&gt;

&lt;p&gt;"How do I monitor memory leaks?"&lt;/p&gt;

&lt;p&gt;"How do I create a reusable validation framework?"&lt;/p&gt;

&lt;p&gt;"How do I manage application updates?"&lt;/p&gt;

&lt;p&gt;"How do I simplify MVVM?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;These problems do not need to be solved independently by every development team.&lt;/p&gt;

&lt;p&gt;Open-source projects allow developers to share solutions.&lt;/p&gt;




&lt;h1&gt;
  
  
  Community Libraries
&lt;/h1&gt;

&lt;p&gt;The ecosystem contains libraries created by Microsoft, commercial vendors, independent developers and community organizations.&lt;/p&gt;

&lt;p&gt;This diversity is important.&lt;/p&gt;

&lt;p&gt;A developer can combine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.NET MAUI
   +
Community Libraries
   +
NuGet Packages
   +
Custom Components
   +
Platform APIs
   +
Cloud Services
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to create a complete application platform tailored to their requirements.&lt;/p&gt;

&lt;p&gt;This is one of the reasons the MAUI ecosystem continues to evolve beyond the core framework.&lt;/p&gt;




&lt;h1&gt;
  
  
  UI Component Ecosystem
&lt;/h1&gt;

&lt;p&gt;Building a modern application often requires more than the standard controls.&lt;/p&gt;

&lt;p&gt;Developers may need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Advanced grids&lt;/li&gt;
&lt;li&gt;Charts&lt;/li&gt;
&lt;li&gt;Data visualization&lt;/li&gt;
&lt;li&gt;PDF viewers&lt;/li&gt;
&lt;li&gt;Rich editors&lt;/li&gt;
&lt;li&gt;Calendars&lt;/li&gt;
&lt;li&gt;Schedulers&lt;/li&gt;
&lt;li&gt;Data forms&lt;/li&gt;
&lt;li&gt;Image processing&lt;/li&gt;
&lt;li&gt;Barcode scanning&lt;/li&gt;
&lt;li&gt;Maps&lt;/li&gt;
&lt;li&gt;Custom navigation&lt;/li&gt;
&lt;li&gt;Advanced pickers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is where commercial and community UI libraries become valuable.&lt;/p&gt;

&lt;p&gt;Companies such as &lt;strong&gt;Syncfusion&lt;/strong&gt; and &lt;strong&gt;Telerik&lt;/strong&gt;, along with many open-source projects, provide additional controls and components for .NET MAUI applications.&lt;/p&gt;

&lt;p&gt;This gives development teams more options than the controls included in the base framework.&lt;/p&gt;




&lt;h1&gt;
  
  
  MVVM and Application Architecture
&lt;/h1&gt;

&lt;p&gt;A large application cannot be maintained effectively by putting everything inside a page's code-behind.&lt;/p&gt;

&lt;p&gt;Architecture becomes essential.&lt;/p&gt;

&lt;p&gt;Common patterns used with MAUI include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MVVM&lt;/li&gt;
&lt;li&gt;Dependency Injection&lt;/li&gt;
&lt;li&gt;Repository pattern&lt;/li&gt;
&lt;li&gt;Service-based architecture&lt;/li&gt;
&lt;li&gt;Clean Architecture&lt;/li&gt;
&lt;li&gt;Modular architecture&lt;/li&gt;
&lt;li&gt;Event-driven approaches&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;MVVM remains particularly common in the MAUI ecosystem.&lt;/p&gt;

&lt;p&gt;A simplified architecture might look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;View
 │
 ▼
ViewModel
 │
 ▼
Application Services
 │
 ▼
Repositories
 │
 ▼
API / Database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This separation makes applications easier to test, maintain and evolve.&lt;/p&gt;




&lt;h1&gt;
  
  
  Networking
&lt;/h1&gt;

&lt;p&gt;Almost every modern mobile application communicates with a backend.&lt;/p&gt;

&lt;p&gt;Therefore networking is a major part of the MAUI ecosystem.&lt;/p&gt;

&lt;p&gt;Applications commonly interact with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;REST APIs&lt;/li&gt;
&lt;li&gt;GraphQL APIs&lt;/li&gt;
&lt;li&gt;WebSockets&lt;/li&gt;
&lt;li&gt;SignalR&lt;/li&gt;
&lt;li&gt;gRPC&lt;/li&gt;
&lt;li&gt;Cloud services&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Developers can use technologies such as &lt;code&gt;HttpClient&lt;/code&gt;, &lt;code&gt;HttpClientFactory&lt;/code&gt;, Refit-style libraries, serialization libraries and custom networking abstractions.&lt;/p&gt;

&lt;p&gt;Production applications also need to consider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Retry policies&lt;/li&gt;
&lt;li&gt;Timeout handling&lt;/li&gt;
&lt;li&gt;Offline scenarios&lt;/li&gt;
&lt;li&gt;Authentication&lt;/li&gt;
&lt;li&gt;Token refresh&lt;/li&gt;
&lt;li&gt;Request cancellation&lt;/li&gt;
&lt;li&gt;Network monitoring&lt;/li&gt;
&lt;li&gt;Error handling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The networking layer can become an ecosystem of its own.&lt;/p&gt;




&lt;h1&gt;
  
  
  Offline-First Applications
&lt;/h1&gt;

&lt;p&gt;One of the interesting areas of MAUI development is offline-first architecture.&lt;/p&gt;

&lt;p&gt;Mobile applications cannot always assume that the network is available.&lt;/p&gt;

&lt;p&gt;Users may:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lose connectivity&lt;/li&gt;
&lt;li&gt;Move between networks&lt;/li&gt;
&lt;li&gt;Enter low-signal areas&lt;/li&gt;
&lt;li&gt;Use applications while travelling&lt;/li&gt;
&lt;li&gt;Need to work with cached information&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An offline-first application can use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User
  │
  ▼
MAUI Application
  │
  ├── Local Database
  │
  ├── Local Cache
  │
  └── Sync Engine
          │
          ▼
       Backend
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application can continue working locally and synchronize changes when connectivity returns.&lt;/p&gt;

&lt;p&gt;This creates opportunities for specialized libraries and frameworks around:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Data synchronization&lt;/li&gt;
&lt;li&gt;Conflict resolution&lt;/li&gt;
&lt;li&gt;Local caching&lt;/li&gt;
&lt;li&gt;Background synchronization&lt;/li&gt;
&lt;li&gt;Incremental synchronization&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are important areas for enterprise MAUI applications.&lt;/p&gt;




&lt;h1&gt;
  
  
  Database Ecosystem
&lt;/h1&gt;

&lt;p&gt;MAUI applications can use several types of data storage.&lt;/p&gt;

&lt;p&gt;For local storage, developers may use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SQLite&lt;/li&gt;
&lt;li&gt;Preferences&lt;/li&gt;
&lt;li&gt;Secure storage&lt;/li&gt;
&lt;li&gt;Files&lt;/li&gt;
&lt;li&gt;Local caches&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For remote storage, applications may communicate with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SQL Server&lt;/li&gt;
&lt;li&gt;PostgreSQL&lt;/li&gt;
&lt;li&gt;MySQL&lt;/li&gt;
&lt;li&gt;MongoDB&lt;/li&gt;
&lt;li&gt;Azure services&lt;/li&gt;
&lt;li&gt;Firebase&lt;/li&gt;
&lt;li&gt;REST APIs&lt;/li&gt;
&lt;li&gt;GraphQL services&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A well-designed application usually keeps the database implementation behind an abstraction rather than allowing the UI to directly access storage.&lt;/p&gt;




&lt;h1&gt;
  
  
  Cloud Integration
&lt;/h1&gt;

&lt;p&gt;Modern mobile applications rarely operate independently.&lt;/p&gt;

&lt;p&gt;They are usually part of a larger cloud architecture.&lt;/p&gt;

&lt;p&gt;A MAUI application can communicate with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Azure&lt;/li&gt;
&lt;li&gt;AWS&lt;/li&gt;
&lt;li&gt;Google Cloud&lt;/li&gt;
&lt;li&gt;Firebase&lt;/li&gt;
&lt;li&gt;Custom APIs&lt;/li&gt;
&lt;li&gt;Serverless functions&lt;/li&gt;
&lt;li&gt;Cloud databases&lt;/li&gt;
&lt;li&gt;Identity platforms&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A typical architecture could look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;               Mobile Application
                      │
                      ▼
                 API Gateway
                      │
             ┌────────┴────────┐
             │                 │
          Backend          Authentication
             │                 │
       ┌─────┴─────┐           │
       │           │           │
   Database      Storage    Identity
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes MAUI a client technology within a much larger application ecosystem.&lt;/p&gt;




&lt;h1&gt;
  
  
  Authentication and Security
&lt;/h1&gt;

&lt;p&gt;Security cannot be treated as an optional feature.&lt;/p&gt;

&lt;p&gt;Production applications commonly require:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OAuth&lt;/li&gt;
&lt;li&gt;OpenID Connect&lt;/li&gt;
&lt;li&gt;JWT&lt;/li&gt;
&lt;li&gt;Secure token storage&lt;/li&gt;
&lt;li&gt;Biometric authentication&lt;/li&gt;
&lt;li&gt;Certificate handling&lt;/li&gt;
&lt;li&gt;Encryption&lt;/li&gt;
&lt;li&gt;Secure API communication&lt;/li&gt;
&lt;li&gt;Application secrets management&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;MAUI provides access to platform security capabilities, while the wider .NET ecosystem provides libraries and services for implementing secure application architectures.&lt;/p&gt;




&lt;h1&gt;
  
  
  Device Capabilities
&lt;/h1&gt;

&lt;p&gt;One of the defining characteristics of mobile applications is their ability to interact with the device.&lt;/p&gt;

&lt;p&gt;MAUI applications can work with capabilities such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Camera&lt;/li&gt;
&lt;li&gt;Microphone&lt;/li&gt;
&lt;li&gt;GPS&lt;/li&gt;
&lt;li&gt;Bluetooth&lt;/li&gt;
&lt;li&gt;Sensors&lt;/li&gt;
&lt;li&gt;Notifications&lt;/li&gt;
&lt;li&gt;Contacts&lt;/li&gt;
&lt;li&gt;Phone functionality&lt;/li&gt;
&lt;li&gt;Files&lt;/li&gt;
&lt;li&gt;Network information&lt;/li&gt;
&lt;li&gt;Battery information&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sometimes the abstraction provided by MAUI is sufficient.&lt;/p&gt;

&lt;p&gt;Sometimes an application needs platform-specific implementation.&lt;/p&gt;

&lt;p&gt;That is not a weakness of the ecosystem.&lt;/p&gt;

&lt;p&gt;It is a natural part of cross-platform development.&lt;/p&gt;

&lt;p&gt;A good MAUI architecture allows shared code and platform-specific code to coexist.&lt;/p&gt;




&lt;h1&gt;
  
  
  Platform-Specific Development
&lt;/h1&gt;

&lt;p&gt;"Write once, run everywhere" does not mean "everything behaves identically everywhere."&lt;/p&gt;

&lt;p&gt;Android, iOS, macOS and Windows have different operating-system behaviors.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 Shared MAUI Code
                       │
       ┌───────────────┼───────────────┐
       │               │               │
    Android           iOS            Windows
       │               │               │
 Android APIs      Apple APIs     Windows APIs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The MAUI ecosystem therefore includes platform-specific development.&lt;/p&gt;

&lt;p&gt;Understanding native platforms remains valuable.&lt;/p&gt;

&lt;p&gt;A strong MAUI developer benefits from knowing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Android lifecycle&lt;/li&gt;
&lt;li&gt;iOS lifecycle&lt;/li&gt;
&lt;li&gt;Permissions&lt;/li&gt;
&lt;li&gt;Background execution&lt;/li&gt;
&lt;li&gt;Notifications&lt;/li&gt;
&lt;li&gt;App signing&lt;/li&gt;
&lt;li&gt;Platform APIs&lt;/li&gt;
&lt;li&gt;Native debugging&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;MAUI reduces duplication, but it does not eliminate the importance of platform knowledge.&lt;/p&gt;




&lt;h1&gt;
  
  
  Testing
&lt;/h1&gt;

&lt;p&gt;A mature ecosystem also needs testing.&lt;/p&gt;

&lt;p&gt;MAUI applications can benefit from multiple layers of testing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Unit Tests
    ↓
Service Tests
    ↓
ViewModel Tests
    ↓
Integration Tests
    ↓
UI Tests
    ↓
Device Testing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Testing becomes particularly important when applications target multiple operating systems.&lt;/p&gt;

&lt;p&gt;A feature that works on Android may still behave differently on iOS or Windows.&lt;/p&gt;

&lt;p&gt;Therefore, cross-platform testing is an important part of the ecosystem.&lt;/p&gt;




&lt;h1&gt;
  
  
  CI/CD and App Distribution
&lt;/h1&gt;

&lt;p&gt;Building an application is only part of the journey.&lt;/p&gt;

&lt;p&gt;Eventually, the application needs to reach users.&lt;/p&gt;

&lt;p&gt;MAUI projects can integrate with CI/CD platforms such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub Actions&lt;/li&gt;
&lt;li&gt;Azure DevOps&lt;/li&gt;
&lt;li&gt;Codemagic&lt;/li&gt;
&lt;li&gt;Other cloud build systems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A typical pipeline may perform:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Git Push
   ↓
Restore
   ↓
Build
   ↓
Test
   ↓
Sign
   ↓
Package
   ↓
Publish
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For mobile applications, signing and distribution add another level of complexity.&lt;/p&gt;

&lt;p&gt;Developers need to manage:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Android signing&lt;/li&gt;
&lt;li&gt;iOS certificates&lt;/li&gt;
&lt;li&gt;Provisioning profiles&lt;/li&gt;
&lt;li&gt;App Store configuration&lt;/li&gt;
&lt;li&gt;Google Play configuration&lt;/li&gt;
&lt;li&gt;Versioning&lt;/li&gt;
&lt;li&gt;Release channels&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The ecosystem around build automation is therefore just as important as the application framework itself.&lt;/p&gt;




&lt;h1&gt;
  
  
  Observability
&lt;/h1&gt;

&lt;p&gt;Production applications need visibility.&lt;/p&gt;

&lt;p&gt;When an application is running on thousands of devices, developers need answers to questions such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is the application crashing?&lt;/li&gt;
&lt;li&gt;Which API is failing?&lt;/li&gt;
&lt;li&gt;Which version has the problem?&lt;/li&gt;
&lt;li&gt;How long are requests taking?&lt;/li&gt;
&lt;li&gt;Which devices are affected?&lt;/li&gt;
&lt;li&gt;Are users experiencing synchronization failures?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This creates another ecosystem around:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Logging&lt;/li&gt;
&lt;li&gt;Crash reporting&lt;/li&gt;
&lt;li&gt;Metrics&lt;/li&gt;
&lt;li&gt;Distributed tracing&lt;/li&gt;
&lt;li&gt;Application telemetry&lt;/li&gt;
&lt;li&gt;OpenTelemetry&lt;/li&gt;
&lt;li&gt;Cloud monitoring&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Observability transforms an application from a black box into something developers can understand and improve.&lt;/p&gt;




&lt;h1&gt;
  
  
  Localization
&lt;/h1&gt;

&lt;p&gt;Global applications need localization.&lt;/p&gt;

&lt;p&gt;Localization is more than translating strings.&lt;/p&gt;

&lt;p&gt;It can involve:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Language&lt;/li&gt;
&lt;li&gt;Date formats&lt;/li&gt;
&lt;li&gt;Number formats&lt;/li&gt;
&lt;li&gt;Currency&lt;/li&gt;
&lt;li&gt;Time zones&lt;/li&gt;
&lt;li&gt;Right-to-left layouts&lt;/li&gt;
&lt;li&gt;Cultural conventions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This creates opportunities for specialized localization libraries and developer tooling.&lt;/p&gt;

&lt;p&gt;For organizations building applications for multiple markets, localization can become a core part of the architecture.&lt;/p&gt;




&lt;h1&gt;
  
  
  Developer Tooling
&lt;/h1&gt;

&lt;p&gt;The ecosystem is also about developer productivity.&lt;/p&gt;

&lt;p&gt;Developers increasingly use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CLI tools&lt;/li&gt;
&lt;li&gt;Code generators&lt;/li&gt;
&lt;li&gt;Project templates&lt;/li&gt;
&lt;li&gt;Analyzers&lt;/li&gt;
&lt;li&gt;Source generators&lt;/li&gt;
&lt;li&gt;AI coding assistants&lt;/li&gt;
&lt;li&gt;Documentation generators&lt;/li&gt;
&lt;li&gt;Build automation tools&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The future MAUI ecosystem will likely include more specialized tools that automate repetitive development tasks.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Developer
   │
   ├── CLI Tools
   ├── AI Assistants
   ├── Templates
   ├── Generators
   ├── Analyzers
   └── Automation
            │
            ▼
       MAUI Project
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  The Rise of AI-Assisted Development
&lt;/h1&gt;

&lt;p&gt;AI is changing how developers discover and consume libraries.&lt;/p&gt;

&lt;p&gt;Developers increasingly ask AI coding assistants questions such as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"What NuGet package can I use for this?"&lt;/p&gt;

&lt;p&gt;"How do I implement this in .NET MAUI?"&lt;/p&gt;

&lt;p&gt;"Show me an example using this library."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This creates a new challenge for library authors.&lt;/p&gt;

&lt;p&gt;Having a package published on NuGet is no longer enough.&lt;/p&gt;

&lt;p&gt;Projects need to be discoverable through:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Good documentation&lt;/li&gt;
&lt;li&gt;Clear README files&lt;/li&gt;
&lt;li&gt;API references&lt;/li&gt;
&lt;li&gt;Code examples&lt;/li&gt;
&lt;li&gt;Search-friendly terminology&lt;/li&gt;
&lt;li&gt;GitHub metadata&lt;/li&gt;
&lt;li&gt;NuGet metadata&lt;/li&gt;
&lt;li&gt;Structured documentation&lt;/li&gt;
&lt;li&gt;AI-friendly project information&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The ecosystem is therefore becoming both &lt;strong&gt;developer-readable and machine-readable&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  Documentation Is Part of the Ecosystem
&lt;/h1&gt;

&lt;p&gt;A library without documentation is difficult to adopt.&lt;/p&gt;

&lt;p&gt;Good documentation should answer:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What problem does this project solve?&lt;/li&gt;
&lt;li&gt;Who should use it?&lt;/li&gt;
&lt;li&gt;How do I install it?&lt;/li&gt;
&lt;li&gt;How do I configure it?&lt;/li&gt;
&lt;li&gt;How do I use it?&lt;/li&gt;
&lt;li&gt;What platforms are supported?&lt;/li&gt;
&lt;li&gt;What are the limitations?&lt;/li&gt;
&lt;li&gt;Where can I report issues?&lt;/li&gt;
&lt;li&gt;How can I contribute?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For open-source projects, documentation is not secondary work.&lt;/p&gt;

&lt;p&gt;It is part of the product.&lt;/p&gt;




&lt;h1&gt;
  
  
  Community Matters
&lt;/h1&gt;

&lt;p&gt;Technology ecosystems are ultimately built by people.&lt;/p&gt;

&lt;p&gt;Developers share:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Libraries&lt;/li&gt;
&lt;li&gt;Tutorials&lt;/li&gt;
&lt;li&gt;Blog posts&lt;/li&gt;
&lt;li&gt;Videos&lt;/li&gt;
&lt;li&gt;GitHub repositories&lt;/li&gt;
&lt;li&gt;Bug fixes&lt;/li&gt;
&lt;li&gt;Discussions&lt;/li&gt;
&lt;li&gt;Samples&lt;/li&gt;
&lt;li&gt;Conference talks&lt;/li&gt;
&lt;li&gt;Community projects&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A healthy ecosystem encourages developers to move from consumers to contributors.&lt;/p&gt;

&lt;p&gt;Someone might begin by installing a NuGet package.&lt;/p&gt;

&lt;p&gt;Later, they report an issue.&lt;/p&gt;

&lt;p&gt;Then they submit a pull request.&lt;/p&gt;

&lt;p&gt;Eventually, they may create their own library.&lt;/p&gt;

&lt;p&gt;That cycle is how ecosystems grow.&lt;/p&gt;




&lt;h1&gt;
  
  
  NuvyntraLabs and the .NET MAUI Ecosystem
&lt;/h1&gt;

&lt;p&gt;Projects such as &lt;strong&gt;NuvyntraLabs&lt;/strong&gt; can become part of this larger ecosystem by creating open-source tools, libraries and developer resources around .NET MAUI.&lt;/p&gt;

&lt;p&gt;The goal does not need to be replacing existing frameworks.&lt;/p&gt;

&lt;p&gt;Instead, the focus can be on solving the practical problems developers encounter while building real applications.&lt;/p&gt;

&lt;p&gt;This can include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MAUI toolkits&lt;/li&gt;
&lt;li&gt;NuGet libraries&lt;/li&gt;
&lt;li&gt;Developer CLI tools&lt;/li&gt;
&lt;li&gt;Localization utilities&lt;/li&gt;
&lt;li&gt;Networking helpers&lt;/li&gt;
&lt;li&gt;Application utilities&lt;/li&gt;
&lt;li&gt;MVVM tools&lt;/li&gt;
&lt;li&gt;Performance utilities&lt;/li&gt;
&lt;li&gt;Developer documentation&lt;/li&gt;
&lt;li&gt;Samples&lt;/li&gt;
&lt;li&gt;Community projects&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The value of an ecosystem comes from the connections between these projects.&lt;/p&gt;

&lt;p&gt;One library can complement another.&lt;/p&gt;

&lt;p&gt;A CLI tool can automate a library's setup.&lt;/p&gt;

&lt;p&gt;Documentation can teach developers how to combine them.&lt;/p&gt;

&lt;p&gt;Community members can contribute improvements.&lt;/p&gt;




&lt;h1&gt;
  
  
  Building an Ecosystem Step by Step
&lt;/h1&gt;

&lt;p&gt;An ecosystem does not need hundreds of projects on day one.&lt;/p&gt;

&lt;p&gt;It can start with one useful library.&lt;/p&gt;

&lt;p&gt;Then another.&lt;/p&gt;

&lt;p&gt;Then documentation.&lt;/p&gt;

&lt;p&gt;Then examples.&lt;/p&gt;

&lt;p&gt;Then developer tools.&lt;/p&gt;

&lt;p&gt;Then community participation.&lt;/p&gt;

&lt;p&gt;A possible progression looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;          Useful Library
                 │
                 ▼
          Documentation
                 │
                 ▼
             Examples
                 │
                 ▼
          Developer Tools
                 │
                 ▼
             Community
                 │
                 ▼
           Contributions
                 │
                 ▼
          Growing Ecosystem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach creates sustainable growth.&lt;/p&gt;




&lt;h1&gt;
  
  
  What Does the Future Look Like?
&lt;/h1&gt;

&lt;p&gt;The MAUI ecosystem is likely to continue evolving alongside the broader .NET ecosystem.&lt;/p&gt;

&lt;p&gt;Several areas are particularly interesting:&lt;/p&gt;

&lt;h3&gt;
  
  
  AI-assisted development
&lt;/h3&gt;

&lt;p&gt;AI coding assistants can make it easier to discover, understand and integrate MAUI libraries.&lt;/p&gt;

&lt;h3&gt;
  
  
  Developer automation
&lt;/h3&gt;

&lt;p&gt;CLI tools and generators can reduce repetitive application-development tasks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Offline-first applications
&lt;/h3&gt;

&lt;p&gt;As applications become more distributed, synchronization and local-first architectures will remain important.&lt;/p&gt;

&lt;h3&gt;
  
  
  Observability
&lt;/h3&gt;

&lt;p&gt;Production applications will increasingly require built-in telemetry and diagnostics.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cross-platform tooling
&lt;/h3&gt;

&lt;p&gt;Developers will continue looking for ways to reduce platform-specific complexity without losing access to native capabilities.&lt;/p&gt;

&lt;h3&gt;
  
  
  Open-source collaboration
&lt;/h3&gt;

&lt;p&gt;Community libraries will continue filling specialized requirements that cannot all be addressed by a single framework.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Bigger Picture
&lt;/h1&gt;

&lt;p&gt;.NET MAUI should not be viewed simply as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"A framework for making Android and iOS applications."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It is better understood as one component of a larger technology ecosystem.&lt;/p&gt;

&lt;p&gt;That ecosystem includes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                         .NET
                          │
                       .NET MAUI
                          │
        ┌─────────────────┼─────────────────┐
        │                 │                 │
       UI            Architecture       Platforms
        │                 │                 │
        ├────────────── Libraries ──────────┤
        │                 │                 │
     NuGet            Open Source         Cloud
        │                 │                 │
        ├──────────── Developer Tools ──────┤
        │                 │                 │
       AI                CI/CD          Observability
        │                 │                 │
        └──────────── Community ────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every component contributes to the developer experience.&lt;/p&gt;




&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;The future of .NET MAUI is not only about the features included in the framework itself.&lt;/p&gt;

&lt;p&gt;It is about what developers build &lt;strong&gt;around it&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Libraries make development faster.&lt;/p&gt;

&lt;p&gt;NuGet makes distribution easier.&lt;/p&gt;

&lt;p&gt;Open source makes collaboration possible.&lt;/p&gt;

&lt;p&gt;Cloud services extend application capabilities.&lt;/p&gt;

&lt;p&gt;CI/CD makes delivery repeatable.&lt;/p&gt;

&lt;p&gt;Observability makes production applications understandable.&lt;/p&gt;

&lt;p&gt;AI makes discovery and development increasingly accessible.&lt;/p&gt;

&lt;p&gt;And communities bring everything together.&lt;/p&gt;

&lt;p&gt;That is what makes an ecosystem powerful.&lt;/p&gt;

&lt;p&gt;For developers, the opportunity is not simply to consume the ecosystem.&lt;/p&gt;

&lt;p&gt;It is to contribute to it.&lt;/p&gt;

&lt;p&gt;Build a library.&lt;/p&gt;

&lt;p&gt;Create a tool.&lt;/p&gt;

&lt;p&gt;Write documentation.&lt;/p&gt;

&lt;p&gt;Publish a sample.&lt;/p&gt;

&lt;p&gt;Fix an issue.&lt;/p&gt;

&lt;p&gt;Share an idea.&lt;/p&gt;

&lt;p&gt;Help another developer.&lt;/p&gt;

&lt;p&gt;Every contribution, large or small, can become part of the larger .NET MAUI ecosystem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Build. Share. Collaborate. Contribute.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That is how an ecosystem grows.&lt;/p&gt;




&lt;p&gt;By &lt;a href="https://www.linkedin.com/in/niladri-padhy-7ab41626" rel="noopener noreferrer"&gt;Admin&lt;/a&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>dotnet</category>
      <category>mobile</category>
      <category>softwaredevelopment</category>
    </item>
  </channel>
</rss>
