- Maui supports three types (kinds; techniques) of navigation.
- The type used is determined by
Application.Current.MainPage
.
- The initial type is set in App.xaml.cs / App constructor, by this line:
MainPage = new ...; // Creates a new instance.
// OR Use DI and `AppServiceProvider` (later in this answer):
MainPage = AppServiceProvider.GetService<...>();
Navigation Type 1: Set the page directly
IMPORTANT: With this technique, NONE of the Shell layout or Shell navigation features exist. App layout, and navigation, is entirely up to you.
This acts like your requirement
I never need a previous page to remain alive.
To begin this, set:
MainPage = new MyFirstPage(); // Creates a new instance.
// OR Use DI and `AppServiceProvider` (later in this answer):
MainPage = AppServiceProvider.GetService<MyFirstPage>();
To go to another page, do:
Application.Current.MainPage = new MyNextPage(); // Creates a new instance.
// OR Use DI and `AppServiceProvider` (later in this answer):
Application.Current.MainPage = AppServiceProvider.GetService<MyNextPage>();
See Sutts answer for a nice example of this direct page setting. That answer uses a different technique for getting at Service Provider.
Navigation Type 2: NavigationPage
IMPORTANT: With this technique, NONE of the Shell layout or Shell navigation features exist. Navigation uses NavigationPage commands. App layout is entirely up to you.
This is a simpler "navigation stack" paradigm than AppShell.
It gives you direct control over the nav stack. No routes or query parameters. Just pushing and popping pages.
To begin this, set:
MainPage = new NavigationPage(new MyFirstPage()); // Creates a new instance.
// OR Use DI and `AppServiceProvider` (later in this answer):
MainPage = new NavigationPage(AppServiceProvider.GetService<MyFirstPage>());
To go to another page, REPLACING the current stack of pages, do:
// NOTE: The root (first) page is always kept.
await Navigation.PopToRootAsync();
// "new ..."
await Navigation.PushAsync(new MyNextPage()); // Creates a new instance.
// OR Use DI and `AppServiceProvider` (later in this answer):
await Navigation.PushAsync(AppServiceProvider.GetService<MyNextPage>());
To go to another page, pushing it on to the stack (so can go back), do:
await Navigation.PushAsync(new MyNextPage()); // Creates a new instance.
// OR Use DI and `AppServiceProvider` (later in this answer):
await Navigation.PushAsync(AppServiceProvider.GetService<MyNextPage>());
Navigation Type 3: Shell Navigation
WARNING (25-July-2023): it has been reported that Maui currently NEVER DISPOSES pages. Therefore, having only one tab does not (currently) help minimize which pages are kept. This answer section on Shell won't help minimize memory usage, until Maui is capable of disposing of pages.
Based on other StackOverflow questions I've seen, AppShell keeps the "root" of EACH TAB around forever. [Unless a new option is added, this will be true even if-and-when Maui is capable of disposing of pages. Tabs are considered "persistent".]
Therefore, to satisfy the requirement "don't keep other pages around", DO NOT do the standard technique of defining your pages "inside" Shell's XAML, as tabs:
<!-- This creates MULTIPLE TABS; these seem to "stick" in memory -->
<!-- DO NOT do this, if you want memory released -->
<Shell ...>
<ShellContent ... />
<ShellContent ... />
<ShellContent ... />
</Shell>
Instead, have a single ShellContent. From this, you navigate to other pages, that are not part of Shell's hierarchy:
<Shell ...
<!-- Only create ONE TAB -->
<ShellContent ... Route="MyRoot" /> <!-- aka "MyFirstPage" -->
<!-- NO MORE "ShellContents". -->
</Shell>
in Shell code-behind:
// Define routes for pages that are NOT part of Shell's XAML.
Routing.RegisterRoute("MyRoot/MyNextPage", typeof(MyNextPage));
navigate to another page:
// This REPLACES nav stack with MyRoot, followed by MyNextPage.
// A limitation of shell is that "//MyNextPage" is not valid,
// unless "MyNextPage" is a TAB as described above. We are avoiding multiple tabs.
await Shell.Current.GoToAsync("//MyRoot/MyNextPage");
Similar to NavigationPage example, the FIRST page (root of the tab) stays in memory.
EXTRA INFO
AppServiceProvider
: USE SERVICE PROVIDER TO AVOID "new MyPage();"
To avoid the memory issues from views/pages not being disposed, re-use the page instances.
Classes used with DI need to be registered at app startup:
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
=> MauiApp.CreateBuilder()
.UseMauiApp<App>()
// Omitted for brevity
.RegisterViewsAndViewModels()
.Build();
public static MauiAppBuilder RegisterViewsAndViewModels(this MauiAppBuilder builder)
{
// Singletons: Re-use page instances.
builder.Services.AddSingleton<MainPage>();
builder.Services.AddSingleton<MyNextPage>();
// Singletons: ViewModels that keep their data between appearances of page.
builder.Services.AddSingleton<SomeViewModel>();
// Transients: ViewModels that start "clean" (default values) on each appearance of page.
builder.Services.AddTransient<SomeViewModel2>();
// Returning the builder supports "chaining" with "."s.
return builder;
}
}
Now that pages and their view models are registered, can automatically "inject" the view model into the page, by adding it as a parameter to the constructor:
public partial class MyPage : ContentPage
{
public MyPage(MyViewModel vm)
{
InitializeComponent();
// OPTIONAL: Convenience for accessing vm properties in code.
VM = vm;
BindingContext = vm;
}
// OPTIONAL: Convenience for accessing vm properties in code.
private MyViewModel VM;
}
In Marc Fabregat's answer to a DI question, there is code to create a static class that gives convenient access to Maui's Dependency Injection Container (an IServiceProvider
):
public static class AppServiceProvider
{
public static TService GetService<TService>()
=> Current.GetService<TService>();
public static IServiceProvider Current
=>
#if WINDOWS10_0_17763_0_OR_GREATER
MauiWinUIApplication.Current.Services;
#elif ANDROID
MauiApplication.Current.Services;
#elif IOS || MACCATALYST
MauiUIApplicationDelegate.Current.Services;
#else
null;
#endif
}
That implementation of AppServiceProvider works even within Maui App constructor; we rely on that in code a bit later below.
This shows a given page, without using new MyPage();
(which would create a new instance each time):
Application.Current.MainPage = AppServiceProvider.GetService<MyPage>();
Set the app's initial page in App.xaml.cs
's constructor:
MainPage = AppServiceProvider.GetService<MyFirstPage>();