doc/articles/guides/xf-migration/navigation.md
This guide explains how to migrate navigation patterns from Xamarin.Forms to Uno Platform. While the underlying concepts are similar, the APIs and approaches differ between the two frameworks.
Xamarin.Forms uses a stack-based navigation model with the INavigation interface:
Navigation property on pagesNavigationPage provides the navigation chrome (back button, title bar)Uno Platform uses the WinUI Frame control for navigation:
Frame maintains the navigation stackType rather than instanceXamarin.Forms:
await Navigation.PushAsync(new DetailPage());
Uno Platform:
Frame.Navigate(typeof(DetailPage));
Xamarin.Forms:
await Navigation.PopAsync();
Uno Platform:
if (Frame.CanGoBack)
{
Frame.GoBack();
}
Xamarin.Forms:
await Navigation.PopToRootAsync();
Uno Platform:
while (Frame.CanGoBack)
{
Frame.GoBack();
}
// Or clear and navigate to root page
Frame.BackStack.Clear();
Frame.Navigate(typeof(MainPage));
Xamarin.Forms:
var detailPage = new DetailPage(itemId);
await Navigation.PushAsync(detailPage);
Uno Platform:
Frame.Navigate(typeof(DetailPage), itemId);
To receive the parameter in the target page:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
if (e.Parameter is int itemId)
{
// Use the parameter
LoadItem(itemId);
}
}
Xamarin.Forms:
var parameters = new NavigationParameters
{
ItemId = 123,
Mode = EditMode.Edit
};
await Navigation.PushAsync(new DetailPage(parameters));
Uno Platform:
var parameters = new NavigationParameters
{
ItemId = 123,
Mode = EditMode.Edit
};
Frame.Navigate(typeof(DetailPage), parameters);
Xamarin.Forms:
await Navigation.PushAsync(new DetailPage(), animated: false);
await Navigation.PopAsync(animated: false);
Uno Platform:
Frame.Navigate(typeof(DetailPage), null,
new SuppressNavigationTransitionInfo());
Frame.GoBack(new SuppressNavigationTransitionInfo());
Xamarin.Forms:
Navigation.RemovePage(pageToRemove);
Uno Platform:
// Remove specific page by index
Frame.BackStack.RemoveAt(index);
// Remove all entries
Frame.BackStack.Clear();
// Remove last entry
if (Frame.BackStack.Count > 0)
{
Frame.BackStack.RemoveAt(Frame.BackStack.Count - 1);
}
Xamarin.Forms:
Navigation.InsertPageBefore(newPage, existingPage);
Uno Platform:
// Insert a page entry into the back stack
var entry = new PageStackEntry(typeof(NewPage), parameter, null);
Frame.BackStack.Insert(index, entry);
Xamarin.Forms:
await Navigation.PushModalAsync(new LoginPage());
Uno Platform:
// Option 1: Use ContentDialog for modal dialogs
var dialog = new LoginDialog();
await dialog.ShowAsync();
// Option 2: Navigate in a child Frame
var modalFrame = new Frame();
modalFrame.Navigate(typeof(LoginPage));
// Add modalFrame to visual tree in an overlay
Xamarin.Forms:
await Navigation.PopModalAsync();
Uno Platform:
// For ContentDialog
dialog.Hide();
// For child Frame navigation
// Remove the frame from the visual tree
Xamarin.Forms Shell provides URI-based navigation. For Uno Platform, you have several options:
The recommended approach is to use Uno.Extensions.Navigation which provides similar URI-based routing:
// Register routes
services.AddSingleton<INavigator, Navigator>();
// Navigate by route
await navigator.NavigateRouteAsync(this, "app/details/123");
For simpler apps, use Frame directly with a navigation service:
public class NavigationService : INavigationService
{
private Frame _frame;
public void Navigate(Type pageType, object parameter = null)
{
_frame.Navigate(pageType, parameter);
}
public void GoBack()
{
if (_frame.CanGoBack)
_frame.GoBack();
}
}
Xamarin.Forms:
<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms">
<ContentPage Title="Tab 1" />
<ContentPage Title="Tab 2" />
<ContentPage Title="Tab 3" />
</TabbedPage>
Uno Platform - Using NavigationView:
<NavigationView PaneDisplayMode="Top">
<NavigationView.MenuItems>
<NavigationViewItem Content="Tab 1" Tag="Tab1Page"/>
<NavigationViewItem Content="Tab 2" Tag="Tab2Page"/>
<NavigationViewItem Content="Tab 3" Tag="Tab3Page"/>
</NavigationView.MenuItems>
<Frame x:Name="ContentFrame"/>
</NavigationView>
Uno Platform - Using TabView (simpler):
<TabView>
<TabViewItem Header="Tab 1">
<local:Tab1Content />
</TabViewItem>
<TabViewItem Header="Tab 2">
<local:Tab2Content />
</TabViewItem>
<TabViewItem Header="Tab 3">
<local:Tab3Content />
</TabViewItem>
</TabView>
Xamarin.Forms:
<FlyoutPage>
<FlyoutPage.Flyout>
<ContentPage Title="Menu">
<!-- Menu content -->
</ContentPage>
</FlyoutPage.Flyout>
<FlyoutPage.Detail>
<NavigationPage>
<x:Arguments>
<local:MainPage />
</x:Arguments>
</NavigationPage>
</FlyoutPage.Detail>
</FlyoutPage>
Uno Platform - Using NavigationView:
<NavigationView x:Name="NavView"
PaneDisplayMode="Left"
IsBackButtonVisible="Collapsed">
<NavigationView.MenuItems>
<NavigationViewItem Content="Home" Icon="Home" Tag="HomePage"/>
<NavigationViewItem Content="Settings" Icon="Setting" Tag="SettingsPage"/>
</NavigationView.MenuItems>
<Frame x:Name="ContentFrame"/>
</NavigationView>
With code-behind for navigation:
private void NavView_ItemInvoked(NavigationView sender,
NavigationViewItemInvokedEventArgs args)
{
if (args.InvokedItemContainer?.Tag is string tag)
{
Type pageType = tag switch
{
"HomePage" => typeof(HomePage),
"SettingsPage" => typeof(SettingsPage),
_ => null
};
if (pageType != null)
{
ContentFrame.Navigate(pageType);
}
}
}
protected override void OnAppearing()
{
base.OnAppearing();
// Page is about to appear
}
protected override void OnDisappearing()
{
base.OnDisappearing();
// Page is about to disappear
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
// Page has been navigated to
// e.Parameter contains the navigation parameter
// e.NavigationMode indicates forward, back, or refresh
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
base.OnNavigatedFrom(e);
// Page is being navigated away from
}
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
base.OnNavigatingFrom(e);
// Navigation is about to occur - can be cancelled
if (HasUnsavedChanges)
{
e.Cancel = true;
ShowSaveDialog();
}
}
View Model:
public class MainViewModel
{
private readonly INavigationService _navigationService;
public ICommand NavigateToDetailsCommand { get; }
public MainViewModel(INavigationService navigationService)
{
_navigationService = navigationService;
NavigateToDetailsCommand = new RelayCommand<int>(NavigateToDetails);
}
private void NavigateToDetails(int itemId)
{
_navigationService.Navigate(typeof(DetailPage), itemId);
}
}
public partial record MainModel(INavigator Navigator)
{
public async ValueTask GoToDetails(int itemId)
{
await Navigator.NavigateViewModelAsync<DetailViewModel>(
this,
data: new DetailData(itemId));
}
}
Routing.RegisterRoute("details", typeof(DetailPage));
await Shell.Current.GoToAsync("details?id=123");
// Configure routes in App.cs
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
RegisterRoutes();
}
private void RegisterRoutes()
{
RouteData.Register("details", typeof(DetailPage));
}
// Navigate using URI
await navigator.NavigateRouteAsync(this, "details?id=123");
Uno Platform:
// Slide in from right
Frame.Navigate(typeof(DetailPage), null,
new SlideNavigationTransitionInfo
{
Effect = SlideNavigationTransitionEffect.FromRight
});
// Drill in
Frame.Navigate(typeof(DetailPage), null,
new DrillInNavigationTransitionInfo());
// Common continue
Frame.Navigate(typeof(DetailPage), null,
new CommonNavigationTransitionInfo());
When migrating navigation:
Navigation.PushAsync() with Frame.Navigate()Navigation.PopAsync() with Frame.GoBack()NavigationEventArgs.ParameterOnNavigatedTo instead of OnAppearingContentDialog or overlay patternsTabbedPage with TabView or NavigationViewFlyoutPage with NavigationViewXamarin.Forms:
protected override bool OnBackButtonPressed()
{
if (HasUnsavedChanges)
{
ShowSaveDialog();
return true; // Prevent back navigation
}
return base.OnBackButtonPressed();
}
Uno Platform:
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
base.OnNavigatingFrom(e);
if (HasUnsavedChanges)
{
e.Cancel = true;
ShowSaveDialog();
}
}
Xamarin.Forms:
// In detail page
MessagingCenter.Send(this, "ItemUpdated", updatedItem);
await Navigation.PopAsync();
// In source page
MessagingCenter.Subscribe<DetailPage, Item>(this, "ItemUpdated",
(sender, item) => RefreshItem(item));
Uno Platform:
// Option 1: Use navigation parameter on back navigation
// Store data in a service or shared state
// Option 2: Use event aggregator or messenger
WeakReferenceMessenger.Default.Send(new ItemUpdatedMessage(updatedItem));
Frame.GoBack();
// In source page
WeakReferenceMessenger.Default.Register<ItemUpdatedMessage>(this,
(r, m) => RefreshItem(m.Item));
Key differences when migrating navigation:
await neededOnNavigatedToFrame.BackStack collectionContentDialog or overlay patterns instead of modal pages