Skip to content

[Docs][Navigation] Provide a list of ALL possible Navigation Setup Variants with samples #3009

@DevTKSS

Description

@DevTKSS

What would you like clarification on

I would appreciate if I would not always be required to lookup the Uno.Extensions/Samples/Playground/* Source Code then instead could get the same Information with eventual future learnings from the regular web Docs of Uno

For example:

protected async override void OnLaunched(LaunchActivatedEventArgs args)
{
MainWindow = new Window();
#if DEBUG
MainWindow.EnableHotReload();
#endif
var appBuilder = this.CreateBuilder(args)
.ConfigureApp()
.UseToolkitNavigation();
MainWindow = appBuilder.Window;
var hostingOption = InitOption.Splash;
switch (hostingOption)
{
case InitOption.AdHocHosting:
// Ad-hoc hosting of Navigation on a UI element with Region.Attached set
_host = appBuilder.Build();
// Create Frame and navigate to MainPage
// MainPage has a ContentControl with Region.Attached set
// which will host navigation
var f = new Frame();
MainWindow.Content = f;
await MainWindow.AttachServicesAsync(_host.Services);
f.Navigate(typeof(MainPage));
await Task.Run(() => _host.StartAsync());
// With this way there's no way to await for navigation to finish
// but it's useful if you want to attach navigation to a UI element
// in an existing application
break;
case InitOption.NavigationRoot:
// Explicitly create the navigation root to use
_host = appBuilder.Build();
var root = new ContentControl
{
HorizontalAlignment = HorizontalAlignment.Stretch,
VerticalAlignment = VerticalAlignment.Stretch,
HorizontalContentAlignment = HorizontalAlignment.Stretch,
VerticalContentAlignment = VerticalAlignment.Stretch
};
MainWindow.Content = root;
var services = await MainWindow.AttachServicesAsync(_host.Services);
var startup = root.HostAsync(services, initialRoute: "");
await Task.Run(() => _host.StartAsync());
// Wait for startup task to complete which will be the end of the
// first navigation
await startup;
break;
case InitOption.InitializeNavigation:
// InitializeNavigationAsync will create the navigation host (ContentControl),
// will invoke the host builder (host is returned) and awaits both start up
// tasks, as well as first navigation
_host = await MainWindow.InitializeNavigationAsync(async () => appBuilder.Build(),
// Option 1: This requires Shell to be the first RouteMap - best for perf as no reflection required
// initialRoute: ""
// Option 2: Specify route name
// initialRoute: "Shell"
// Option 3: Specify the view model. To avoid reflection, you can still define a routemap
initialViewModel: typeof(ShellViewModel)
);
break;
case InitOption.Splash:
// InitializeNavigationAsync (Navigation.Toolkit) uses a LoadingView as navigation host,
// will invoke the host builder (host is returned) and awaits both start up
// tasks, as well as first navigation. In this case the navigation host is an ExtendedSplashScreen
// element, so will show the native splash screen until the first navigation is completed
var appRoot = new AppRoot();
appRoot.SplashScreen.Initialize(MainWindow, args);
MainWindow.Content = appRoot;
_host = await MainWindow.InitializeNavigationAsync(
async () =>
{
// Uncomment to view splashscreen for longer
// await Task.Delay(5000);
return appBuilder.Build();
},
navigationRoot: appRoot.SplashScreen,
// Option 1: This requires Shell to be the first RouteMap - best for perf as no reflection required
// initialRoute: ""
// Option 2: Specify route name
// initialRoute: "Shell"
// Option 3: Specify the view model. To avoid reflection, you can still define a routemap
initialViewModel: typeof(HomeViewModel)
);
break;
case InitOption.AppBuilderShell:
_host = await appBuilder.NavigateAsync<AppRoot>();
break;
case InitOption.NoShellViewModel:
// InitializeNavigationAsync with splash screen and async callback to determine where
// initial navigation should go
var appRootNoShell = new AppRoot();
appRootNoShell.SplashScreen.Initialize(MainWindow, args);
MainWindow.Content = appRootNoShell;
MainWindow.Activate();
_host = await MainWindow.InitializeNavigationAsync(
async () =>
{
return appBuilder.Build();
},
navigationRoot: appRootNoShell.SplashScreen,
initialNavigate: async (sp, nav) =>
{
// Uncomment to view splashscreen for longer
await Task.Delay(5000);
await nav.NavigateViewAsync<HomePage>(this);
}
);
break;
}
var notif = _host!.Services.GetRequiredService<IRouteNotifier>();
notif.RouteChanged += RouteUpdated;
var logger = _host.Services.GetRequiredService<ILogger<App>>();
if (logger.IsEnabled(LogLevel.Trace)) logger.LogTraceMessage("LogLevel:Trace");
if (logger.IsEnabled(LogLevel.Debug)) logger.LogDebugMessage("LogLevel:Debug");
if (logger.IsEnabled(LogLevel.Information)) logger.LogInformationMessage("LogLevel:Information");
if (logger.IsEnabled(LogLevel.Warning)) logger.LogWarningMessage("LogLevel:Warning");
if (logger.IsEnabled(LogLevel.Error)) logger.LogErrorMessage("LogLevel:Error");
if (logger.IsEnabled(LogLevel.Critical)) logger.LogCriticalMessage("LogLevel:Critical");
}
private enum InitOption
{
AdHocHosting,
NavigationRoot,
InitializeNavigation,
Splash,
NoShellViewModel,
AppBuilderShell
}
public void RouteUpdated(object? sender, RouteChangedEventArgs? e)
{
try
{
var rootRegion = e?.Region.Root();
var route = rootRegion?.GetRoute();
if (route is null)
{
return;
}
#if !__WASM__ && !WINUI
CoreApplication.MainView?.DispatcherQueue.TryEnqueue(() =>
{
var appTitle = ApplicationView.GetForCurrentView();
appTitle.Title = "Commerce: " + (route + "").Replace("+", "/");
});
#endif
}
catch (Exception ex)
{
Console.WriteLine("Error: " + ex.Message);
}
}
}

Missing options currently:

  • Different ways and to set up navigation using Shell
  • Configuring SplashScreen without modifing it directly (?) line 103-109
  • AppBuilder Shell (can anyone tell me what this is or for what it is usefull? -> you see, if we would have this in our docs, I wouldn't need to ask 👍 )
  • No Shell VM ( maybe we can even remove the Shell with this if we want to?)

and I would also like to see a full collection of all available Route Registrations, instead of only selected ones:

private static void RegisterRoutes(IViewRegistry views, IRouteRegistry routes)
{
var confirmDialog = new MessageDialogViewMap(
Content: "Confirm this message?",
Title: "Confirm?",
DelayUserInput: true,
DefaultButtonIndex: 1,
Buttons: new DialogAction[]
{
new(Label: "Yeh!",Id:"Y"),
new(Label: "Nah", Id:"N")
}
);
var localizedDialog = new LocalizableMessageDialogViewMap(
Content: localizer => "[localized]Confirm this message?",
Title: localizer => "[localized]Confirm?",
DelayUserInput: true,
DefaultButtonIndex: 1,
Buttons: new LocalizableDialogAction[]
{
new(LabelProvider: localizer=> localizer!["Y"],Id:"Y"),
new(LabelProvider: localizer=> localizer!["N"], Id:"N")
}
);
views.Register(
// Option 1: Specify ShellView in order to customise the shell
//new ViewMap<ShellView, ShellViewModel>(),
// Option 2: Only specify the ShellViewModel - this will inject a FrameView where the subsequent pages will be shown
new ViewMap(ViewModel: typeof(ShellViewModel)),
new ViewMap<HomePage, HomeViewModel>(),
new ViewMap<CodeBehindPage>(),
new ViewMap<VMPage, VMViewModel>(),
new ViewMap<XamlPage, XamlViewModel>(),
new ViewMap<NavigationViewPage, NavigationViewViewModel>(),
new ViewMap<NavContentPage, NavContentViewModel>(Data: new DataMap<NavWidget>()),
new ViewMap<NavContentSecondPage>(),
new ViewMap<TabBarPage>(),
new ViewMap<ContentControlPage>(),
new ViewMap<SecondPage, SecondViewModel>(Data: new DataMap<Widget>(), ResultData: typeof(Country)),
new ViewMap<ThirdPage>(),
new ViewMap<FourthPage, FourthViewModel>(),
new ViewMap<FifthPage, FifthViewModel>(),
new ViewMap<DialogsPage>(),
new ViewMap<SimpleDialog, SimpleViewModel>(),
new ViewMap<ComplexDialog>(),
new ViewMap<ComplexDialogFirstPage>(),
new ViewMap<ComplexDialogSecondPage>(),
new ViewMap<PanelVisibilityPage>(),
new ViewMap<VisualStatesPage>(),
new ViewMap<AdHocPage, AdHocViewModel>(),
new ViewMap<ListPage, ListViewModel>(),
new ViewMap<ItemDetailsPage, ItemDetailsViewModel>(),
new ViewMap<AuthTokenDialog, AuthTokenViewModel>(),
new ViewMap<BasicFlyout, BasicViewModel>(),
new ViewMap<ThemeSwitchPage, ThemeSwitchViewModel>(),
confirmDialog,
localizedDialog
);
// RouteMap required for Shell if initialRoute or initialViewModel isn't specified when calling NavigationHost
routes.Register(
new RouteMap("", View: views.FindByViewModel<ShellViewModel>(),
Nested: new[]
{
new RouteMap("Home",View: views.FindByView<HomePage>()),
new RouteMap("CodeBehind",View: views.FindByView<CodeBehindPage>(), DependsOn: "Home"),
new RouteMap("VM",View: views.FindByView<VMPage>(), DependsOn: "Home"),
new RouteMap("Xaml",View: views.FindByView<XamlPage>(), DependsOn: "Home"),
new RouteMap("NavigationView",View: views.FindByView<NavigationViewPage>(), DependsOn: "Home",
Nested: new[]
{
new RouteMap("NavContent", View: views.FindByViewModel<NavContentViewModel>(),
Nested: new[]
{
new RouteMap("NavContentTabs", IsDefault:true,
Nested: new[]
{
new RouteMap("Tab1", IsDefault:true),
new RouteMap("Tab2")
}),
}),
new RouteMap("NavContentSecond", View: views.FindByView<NavContentSecondPage>(), DependsOn:"NavContent")
}),
new RouteMap("TabBar",View: views.FindByView<TabBarPage>(), DependsOn: "Home"),
new RouteMap("ContentControl",View: views.FindByView<ContentControlPage>(), DependsOn: "Home"),
new RouteMap("Second",View: views.FindByView<SecondPage>(), DependsOn: "Home"),
new RouteMap("Third",View: views.FindByView<ThirdPage>()),
new RouteMap("Fourth",View: views.FindByView<FourthPage>()),
new RouteMap("Fifth",View: views.FindByView<FifthPage>(), DependsOn: "Third"),
new RouteMap("Dialogs",View: views.FindByView<DialogsPage>(),
Nested: new[]
{
new RouteMap("Simple",View: views.FindByView<SimpleDialog>()),
new RouteMap("Complex",View: views.FindByView<ComplexDialog>(),
Nested: new[]
{
new RouteMap("ComplexDialogFirst",View: views.FindByView<ComplexDialogFirstPage>()),
new RouteMap("ComplexDialogSecond",View: views.FindByView<ComplexDialogSecondPage>(), DependsOn: "ComplexDialogFirst")
})
}),
new RouteMap("PanelVisibility",View: views.FindByView<PanelVisibilityPage>()),
new RouteMap("VisualStates",View: views.FindByView<VisualStatesPage>()),
new RouteMap("AdHoc",View: views.FindByViewModel<AdHocViewModel>(),
Nested: new[]
{
new RouteMap("Auth", View: views.FindByView<AuthTokenDialog>())
}),
new RouteMap("List",View: views.FindByViewModel<ListViewModel>()),
new RouteMap("ItemDetails",View: views.FindByViewModel<ItemDetailsViewModel>()),
new RouteMap("Confirm", View: confirmDialog),
new RouteMap("LocalizedConfirm", View: localizedDialog)
}));
}

Or this here:

var services = await MainWindow.AttachServicesAsync(_host.Services);
var startup = root.HostAsync(services, initialRoute: "");

Might even be a partial answer to the usage of Xaml available Attached Property ServiceProvider?

Concern?

  • Usage in industry
  • Clarification of capabilities
  • Getting started with Uno
  • Developing with Uno
  • Contributing to the Uno project
  • Publishing your application
  • Support
  • Other (please specify):

For which Platform

  • All Platforms 🌐
  • Build tasks

Anything else we need to know?

@Jen-Uno some relevant to be documented informations to consider. We have this great sample directly here in the repo, so it even would greatly integrate with [code-csharp [](...)] markdown 🚀 just to find somebody willing to let us consumers know about them 👀 do you think this is possible to get in our future Uno docs?
Could also minimize the Agent-MCPs halucinating about Do's and Don't do's with the Shell 😅

Metadata

Metadata

Assignees

No one assigned

    Labels

    kind/consumer-experienceCategorizes issue or PR as related to improving the experience of consumerskind/documentationCategorizes an issue or PR as relevant to 3rd party dependencies that are consumed by this projecttriage/untriagedIndicates an issue requires triaging or verification.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions