Sumapap.Navigations.Maui 2.0.0

dotnet add package Sumapap.Navigations.Maui --version 2.0.0
                    
NuGet\Install-Package Sumapap.Navigations.Maui -Version 2.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Sumapap.Navigations.Maui" Version="2.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Sumapap.Navigations.Maui" Version="2.0.0" />
                    
Directory.Packages.props
<PackageReference Include="Sumapap.Navigations.Maui" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Sumapap.Navigations.Maui --version 2.0.0
                    
#r "nuget: Sumapap.Navigations.Maui, 2.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Sumapap.Navigations.Maui@2.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Sumapap.Navigations.Maui&version=2.0.0
                    
Install as a Cake Addin
#tool nuget:?package=Sumapap.Navigations.Maui&version=2.0.0
                    
Install as a Cake Tool

Sumapap.Navigations.Maui

NuGet Version NuGet Downloads License GitHub Issues GitHub Stars GitHub Forks Contributions Welcome

💡 Overview

Sumapap.Navigations.Maui is a concrete implementation of the Sumapap.Navigations abstractions specifically designed for .NET MAUI applications. It provides two powerful navigation adapters that work seamlessly with MAUI's navigation systems:

  • PageNavigationAdapter — Traditional page-based navigation with modal support
  • ShellNavigationAdapter — Modern Shell-based navigation with route patterns

This package eliminates boilerplate navigation code, provides type-safe parameter passing, and supports both animated and non-animated transitions across all MAUI platforms (Android, iOS, macOS, Windows).

✨ Why use Sumapap.Navigations.Maui?

  • Zero Boilerplate — No need to manually implement INavigationService
  • Dual Navigation Modes — Choose between Page-based or Shell-based navigation
  • Type-Safe Parameters — Strongly-typed parameter objects with compile-time safety
  • Modal Support — Built-in support for modal presentations
  • Animation Control — Enable or disable navigation animations per operation
  • Query Parameters — Pass complex data via Shell query parameters or Page binding context
  • Route Management — Support for relative, absolute, and stack-clearing Shell routes
  • Cross-Platform — Works identically across Android, iOS, macOS, and Windows

🚀 Quick start

1. Installation

dotnet add package Sumapap.Navigations.Maui

2. Register navigation services

Choose between Page-based or Shell-based navigation:

// In MauiProgram.cs
using Sumapap.Navigations.Maui.DependencyInjection;

public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();

        builder.Services
            .AddSumapap()
            .AddNavigations(nav =>
            {
                // Option 1: Use Page-based navigation
                nav.UsePageNavigation();

                // Option 2: Use Shell-based navigation
                // nav.UseShellNavigation();
            });

        // Register your pages/views
        builder.Services.AddTransient<MainPage>();
        builder.Services.AddTransient<DetailPage>();

        return builder.Build();
    }
}

3. Use navigation in your ViewModels

using Sumapap.Navigations.Abstractions;
using Sumapap.Navigations.Maui.Models;

public class MainViewModel
{
    private readonly INavigationService _navigationService;

    public MainViewModel(INavigationService navigationService)
    {
        _navigationService = navigationService;
    }

    // Simple navigation
    public async Task NavigateToDetailAsync()
    {
        await _navigationService.NavigateToAsync<DetailPage>();
    }

    // Navigation with parameters
    public async Task NavigateWithDataAsync(int itemId)
    {
        var parameters = PageNavigationParams.Create(
            ("ItemId", itemId),
            ("Title", "Item Details")
        );

        await _navigationService.NavigateToAsync<DetailPage>(parameters);
    }

    // Modal navigation
    public async Task ShowModalAsync()
    {
        var parameters = PageNavigationParams.Create(
            PageNavigationMode.Modal,
            ("ShowCloseButton", true)
        );

        await _navigationService.NavigateToAsync<ModalPage>(parameters);
    }
}

🛠 Features and usage

Traditional page-based navigation using MAUI's INavigation interface. Supports both normal (push/pop) and modal presentations.

Basic navigation
using Sumapap.Navigations.Abstractions;
using Sumapap.Navigations.Maui.Models;

// Navigate forward
await _navigationService.NavigateToAsync<SettingsPage>();

// Navigate back
await _navigationService.BackAsync();

// Pop to root
await _navigationService.NavigateToRootAsync<HomePage>();
// Show modal page
var modalParams = PageNavigationParams.Create(
    PageNavigationMode.Modal,
    ("Data", someData)
);
await _navigationService.NavigateToAsync<ModalPage>(modalParams);

// Dismiss modal
var resultParams = PageNavigationParams.Create(
    PageNavigationMode.Modal,
    ("Result", "Saved")
);
await _navigationService.BackAsync(resultParams);
Passing parameters
// Create parameters with query data
var parameters = PageNavigationParams.Create(
    ("UserId", 123),
    ("UserName", "John Doe"),
    ("IsAdmin", true)
);

await _navigationService.NavigateToAsync<UserDetailPage>(parameters);

// The parameters are automatically bound to the target page's BindingContext properties
// UserDetailViewModel.UserId will be set to 123
// UserDetailViewModel.UserName will be set to "John Doe"
// UserDetailViewModel.IsAdmin will be set to true
Animation control
// Disable animation for specific navigation
var params = new PageNavigationParams
{
    Animated = false,
    Query = new NavigationQuery
    {
        ["ItemId"] = 456
    }
};

await _navigationService.NavigateToAsync<DetailPage>(params);
// Empty parameters (animated by default)
var empty = PageNavigationParams.Empty;

// With query parameters
var withQuery = PageNavigationParams.Create(
    ("Key1", "Value1"),
    ("Key2", 42)
);

// With navigation mode
var modal = PageNavigationParams.Create(
    PageNavigationMode.Modal,
    ("Data", someObject)
);

// Full control
var custom = new PageNavigationParams
{
    Mode = PageNavigationMode.Normal,
    Animated = false,
    Query = new NavigationQuery
    {
        ["Param1"] = value1,
        ["Param2"] = value2
    }
};

ShellNavigationAdapter

Modern Shell-based navigation using MAUI's Shell.GoToAsync routing. Supports relative, absolute, and hierarchical route patterns.

Basic navigation
using Sumapap.Navigations.Abstractions;
using Sumapap.Navigations.Maui.Models;

// Navigate forward (relative)
await _navigationService.NavigateToAsync<DetailPage>();

// Navigate back
await _navigationService.BackAsync();

// Navigate to root
await _navigationService.NavigateToRootAsync<MainPage>();
Route patterns
// Relative navigation (push onto current stack)
var relative = ShellNavigationParams.Create(
    ShellNavigationParams.Relative,  // "" - appends to current route
    ("Id", 123)
);
await _navigationService.NavigateToAsync<DetailPage>(relative);

// Absolute navigation (from app root)
var absolute = ShellNavigationParams.Create(
    ShellNavigationParams.Absolute,  // "//" - starts from root
    ("Tab", "Settings")
);
await _navigationService.NavigateToAsync<SettingsPage>(absolute);

// Absolute with stack clear
var clearStack = new ShellNavigationParams
{
    ShellRoute = ShellNavigationParams.AbsoluteClearStack,  // "///" - clears entire stack
    Query = new NavigationQuery { ["Reset"] = true }
};
await _navigationService.NavigateToAsync<LoginPage>(clearStack);
Query parameters
// Shell automatically serializes query parameters to the URL
var parameters = ShellNavigationParams.Create(
    ("ProductId", 789),
    ("Category", "Electronics"),
    ("ShowReviews", true)
);

await _navigationService.NavigateToAsync<ProductPage>(parameters);
// Results in route: ProductPage?ProductId=789&Category=Electronics&ShowReviews=true
// When using NavigateToRootAsync with ShellNavigationAdapter,
// the route is automatically converted to absolute if not already
await _navigationService.NavigateToRootAsync<HomePage>();
// Equivalent to: Shell.GoToAsync("//HomePage")
ShellNavigationParams API
// Empty parameters (relative, animated)
var empty = ShellNavigationParams.Empty;

// With query parameters (relative by default)
var withQuery = ShellNavigationParams.Create(
    ("Key1", "Value1"),
    ("Key2", 42)
);

// With specific route pattern
var absolute = ShellNavigationParams.Create(
    ShellNavigationParams.Absolute,
    ("Data", someValue)
);

// Full control
var custom = new ShellNavigationParams
{
    ShellRoute = ShellNavigationParams.RelativeTo,  // "/"
    Animated = false,
    Query = new NavigationQuery
    {
        ["Param1"] = value1,
        ["Param2"] = value2
    }
};

// Route constants
ShellNavigationParams.Relative              // "" - relative to current
ShellNavigationParams.RelativeTo            // "/" - relative to parent
ShellNavigationParams.Absolute              // "//" - absolute from root
ShellNavigationParams.AbsoluteClearStack    // "///" - absolute + clear stack

MauiNavigationParams

Base class for all MAUI navigation parameters, providing common animation control:

public abstract record MauiNavigationParams : INavigationParams
{
    public bool Animated { get; init; } = true;
}

Both PageNavigationParams and ShellNavigationParams inherit from this base class.

Dictionary-based container for navigation parameters:

var query = new NavigationQuery
{
    ["UserId"] = 123,
    ["UserName"] = "Alice",
    ["Timestamp"] = DateTime.UtcNow
};

// Or use collection initializer in params
var parameters = PageNavigationParams.Create(
    ("Key1", value1),
    ("Key2", value2),
    ("Key3", value3)
);

Enum for specifying page presentation mode:

public enum PageNavigationMode
{
    Normal,  // Standard push/pop navigation
    Modal    // Modal presentation (overlays current page)
}

📋 Complete examples

Example 1: Master-Detail with Page navigation

// MainViewModel.cs
public class MainViewModel
{
    private readonly INavigationService _navigationService;

    public MainViewModel(INavigationService navigationService)
    {
        _navigationService = navigationService;
    }

    public async Task ViewItemDetailsAsync(int itemId)
    {
        var parameters = PageNavigationParams.Create(
            ("ItemId", itemId)
        );

        await _navigationService.NavigateToAsync<DetailPage>(parameters);
    }
}

// DetailViewModel.cs
public class DetailViewModel
{
    public int ItemId { get; set; }  // Automatically populated by NavigationQuery

    public async Task GoBackAsync()
    {
        await _navigationService.BackAsync();
    }
}

Example 2: Shell navigation with tabs

// AppShell.xaml.cs - Register routes
public partial class AppShell : Shell
{
    public AppShell()
    {
        InitializeComponent();

        Routing.RegisterRoute(nameof(DetailPage), typeof(DetailPage));
        Routing.RegisterRoute(nameof(EditPage), typeof(EditPage));
    }
}

// ViewModel
public async Task NavigateToEditAsync(int itemId)
{
    var parameters = ShellNavigationParams.Create(
        ShellNavigationParams.Relative,
        ("ItemId", itemId),
        ("Mode", "Edit")
    );

    await _navigationService.NavigateToAsync<EditPage>(parameters);
}

// Navigate to different tab (absolute)
public async Task SwitchToSettingsTabAsync()
{
    var parameters = new ShellNavigationParams
    {
        ShellRoute = ShellNavigationParams.Absolute
    };

    await _navigationService.NavigateToAsync<SettingsPage>(parameters);
}

Example 3: Modal form with result

// Show modal form
public async Task ShowAddItemModalAsync()
{
    var parameters = PageNavigationParams.Create(
        PageNavigationMode.Modal,
        ("Title", "Add New Item")
    );

    await _navigationService.NavigateToAsync<AddItemPage>(parameters);
}

// In AddItemViewModel - save and dismiss
public async Task SaveAndCloseAsync()
{
    // Save logic...

    var result = PageNavigationParams.Create(
        PageNavigationMode.Modal,
        ("ItemAdded", true),
        ("NewItemId", newItemId)
    );

    await _navigationService.BackAsync(result);
}

Example 4: Multi-level navigation

// Navigate deep into hierarchy
public async Task NavigateToCategoryProductAsync(string category, int productId)
{
    // First navigate to category
    var categoryParams = PageNavigationParams.Create(
        ("Category", category)
    );
    await _navigationService.NavigateToAsync<CategoryPage>(categoryParams);

    // Then navigate to product
    var productParams = PageNavigationParams.Create(
        ("ProductId", productId)
    );
    await _navigationService.NavigateToAsync<ProductPage>(productParams);
}

// Reset to home
public async Task ResetToHomeAsync()
{
    await _navigationService.NavigateToRootAsync<HomePage>();
}

🎯 Best practices

Page/View registration

// Register pages as Transient (new instance per navigation)
builder.Services.AddTransient<MainPage>();
builder.Services.AddTransient<DetailPage>();

// Register ViewModels as Transient
builder.Services.AddTransient<MainViewModel>();
builder.Services.AddTransient<DetailViewModel>();

Shell route registration

// In AppShell.xaml.cs
Routing.RegisterRoute(nameof(DetailPage), typeof(DetailPage));
Routing.RegisterRoute("products/detail", typeof(ProductDetailPage));
Routing.RegisterRoute("settings/profile", typeof(ProfilePage));

ViewModel initialization

// Use properties for navigation parameters (auto-populated)
public class DetailViewModel
{
    public int ItemId { get; set; }
    public string? Title { get; set; }

    // Perform initialization in constructor or property setters
    public DetailViewModel()
    {
        // Don't load data here - properties not set yet
    }

    // Option 1: React to property changes
    private int _itemId;
    public int ItemId
    {
        get => _itemId;
        set
        {
            _itemId = value;
            LoadItemAsync(value);  // Load when parameter is set
        }
    }

    // Option 2: Use INavigationAware (custom interface)
    public void OnNavigatedTo(INavigationParams? parameters)
    {
        LoadItemAsync(ItemId);
    }
}

Error handling

public async Task NavigateWithErrorHandlingAsync()
{
    try
    {
        var parameters = PageNavigationParams.Create(("Id", 123));
        await _navigationService.NavigateToAsync<DetailPage>(parameters);
    }
    catch (InvalidOperationException ex)
    {
        // Page not registered
        await ShowErrorAsync("Navigation failed: " + ex.Message);
    }
    catch (InvalidCastException ex)
    {
        // Wrong parameter type
        await ShowErrorAsync("Invalid parameters: " + ex.Message);
    }
}

Thread safety

// MAUI navigation must run on main thread
// MauiNavigationService handles this automatically
await _navigationService.NavigateToAsync<DetailPage>();

// If implementing custom adapter, use:
await MainThread.InvokeOnMainThreadAsync(async () =>
{
    await Shell.Current.GoToAsync("DetailPage");
});

🔍 Advanced scenarios

Custom navigation adapter

public class CustomNavigationAdapter : INavigationAdapter
{
    public bool CanHandle() => /* your condition */;

    public Task NavigateToAsync<TView>(CancellationToken cancellationToken = default)
    {
        // Your custom implementation
        return Task.CompletedTask;
    }

    // Implement other INavigationService methods...
}

// Register
builder.Services.AddSumapap()
    .AddNavigations(nav =>
    {
        nav.Services.AddSingleton<INavigationAdapter, CustomNavigationAdapter>();
    });

Mixing navigation modes

// Use different adapters in different parts of your app
// Register multiple adapters and choose at runtime

public class HybridNavigationService : INavigationService
{
    private readonly IEnumerable<INavigationAdapter> _adapters;

    public HybridNavigationService(IEnumerable<INavigationAdapter> adapters)
    {
        _adapters = adapters;
    }

    public Task NavigateToAsync<TView>(INavigationParams param, CancellationToken cancellationToken = default)
    {
        var adapter = _adapters.FirstOrDefault(a => a.CanHandle())
            ?? throw new InvalidOperationException("No suitable adapter found");

        return adapter.NavigateToAsync<TView>(param, cancellationToken);
    }
}

🔧 Troubleshooting

Page not found exception

NullReferenceException: Page DetailPage not registered

Solution: Register the page in DI container:

builder.Services.AddTransient<DetailPage>();

InvalidCastException

InvalidCastException: Cannot cast INavigationParams to PageNavigationParams

Solution: Use correct parameter type for the adapter:

// For PageNavigationAdapter, use PageNavigationParams
var pageParams = PageNavigationParams.Create(("Id", 123));

// For ShellNavigationAdapter, use ShellNavigationParams
var shellParams = ShellNavigationParams.Create(("Id", 123));

Shell route not found

Shell: route not found: DetailPage

Solution: Register the route in AppShell:

Routing.RegisterRoute(nameof(DetailPage), typeof(DetailPage));

Parameters not binding

Properties in ViewModel remain null after navigation

Solution: Ensure property names match exactly:

// Navigation
var params = PageNavigationParams.Create(("UserId", 123));

// ViewModel - property name must match "UserId"
public int UserId { get; set; }  // ✓ Correct
public int userid { get; set; }  // ✗ Wrong case
public int ID { get; set; }      // ✗ Wrong name

⚙️ Configuration

Choose navigation adapter

// Page-based navigation (traditional)
builder.Services.AddSumapap()
    .AddNavigations(nav => nav.UsePageNavigation());

// Shell-based navigation (modern)
builder.Services.AddSumapap()
    .AddNavigations(nav => nav.UseShellNavigation());

Default animation setting

Animation is enabled by default. Disable per-navigation:

var params = new PageNavigationParams { Animated = false };
await _navigationService.NavigateToAsync<DetailPage>(params);

⭐ License

This project is licensed under the MIT License. See the LICENSE file for details.

🚩 Contact

☕ Support

If you find this project helpful, consider supporting the developer:

<a href="https://www.buymeacoffee.com/muhammadirwanto" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 60px !important;width: 217px !important;" ></a>

Product Compatible and additional computed target framework versions.
.NET net10.0-android36.0 is compatible.  net10.0-ios26.0 is compatible.  net10.0-maccatalyst26.0 is compatible. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.0.0 90 9/14/2026