Skip to content

Instantly share code, notes, and snippets.

@mzhukovs
Last active February 25, 2020 12:36
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mzhukovs/58c2134a40825a232a249dda91b59ec3 to your computer and use it in GitHub Desktop.
Save mzhukovs/58c2134a40825a232a249dda91b59ec3 to your computer and use it in GitHub Desktop.
Xamarin Forms Helper Class for Navigation with Prism 7.0
/// <summary>
/// Helps with the keys for navigation for Prism, which uses deep linking with support for parameters as a query string, e.g. "MainTabbedPage/NavigationPage/ShowsListPage/DetailPage?id=1&title=First page"
/// In Prism, the concept of navigating to a View or navigating to a ViewModel does not exist. Instead, you simply navigate to an experience, or a unique identifier, which represents the target view you wish to navigate to in your app.
/// Despite this, I prefer mainly relying on ViewModels for ease of code-browsing and organization, so adding the helpers here for that.
/// For more advanced scenarios, can just use the string extensions directly, using nameof(VM) to call them.
/// </summary>
public static class NavHelper
{
#region For Common Scenarios
/// <summary>
/// Use this for most navs - just a basic NavigateAsync to the given ViewModel's Page, with the option to remove the current page from the stack
/// </summary>
/// <typeparam name="TViewModel">The ViewModel Type whose page to navigate to</typeparam>
/// <param name="navService">The NavigationService instance</param>
/// <param name="navParams">Pass in any NavigationParameters as required</param>
/// <param name="asModal">Whether to navigate to the page as a modal</param>
/// <param name="removeMe">True to remove the current page calling the navigation from the stack</param>
/// <returns>Navigation Task</returns>
public static Task NavAsync<TViewModel>(this INavigationService navService, NavigationParameters navParams = null, bool asModal = false, bool removeMe = false)
where TViewModel : ViewModelBase
{
var navPath = typeof(TViewModel).Name.PageKey(asModal)
.AndRemovePreviousIf(removeMe);
return navService.NavigateAsync(navPath, navParams);
}
/// <summary>
/// Add a page to the stack before navigating to the target page, with the option to remove the current page from the stack
/// </summary>
/// <typeparam name="TViewModel1">The ViewModel Type whose page to add to the stack</typeparam>
/// <typeparam name="TViewModel2">The ViewModel Type whose page to navigate to</typeparam>
/// <param name="navService">The NavigationService instance</param>
/// <param name="navParams">Pass in any NavigationParameters as required</param>
/// <param name="asModal">Whether to navigate to the target page as a modal</param>
/// <param name="removeMe">True to remove the current page calling the navigation from the stack</param>
/// <returns>Navigation Task</returns>
public static Task NavAsync<TViewModel1, TViewModel2>(this INavigationService navService, NavigationParameters navParams = null, bool asModal = false, bool removeMe = false)
where TViewModel1 : ViewModelBase where TViewModel2 : ViewModelBase
{
var navPath = typeof(TViewModel1).Name.PageKey()
.AndThen(typeof(TViewModel2).Name.PageKey(asModal))
.AndRemovePreviousIf(removeMe);
return navService.NavigateAsync(navPath, navParams);
}
/// <summary>
/// Navigate to a MultiPage (namely, a TabbedPage or CarouselPage) and select/show one if its specific inner pages (to show first one, can just navigate normally using NavAsync).
/// </summary>
/// <typeparam name="TMultiPageViewModel">The ViewModel Type of the Tabbed/CarouselPage to navigate to</typeparam>
/// <typeparam name="TMultiPageViewModelChild">The ViewModel Type of the page within the Tabbed/CarouselPage to select/show</typeparam>
/// <param name="navService">The NavigationService instance</param>
/// <param name="navParams">Pass in any NavigationParameters as required</param>
/// <param name="removeMe">True to remove the current page calling the navigation from the stack</param>
/// <returns>Navigation Task</returns>
public static Task NavToMultiPageAsync<TMultiPageViewModel, TMultiPageViewModelChild>(this INavigationService navService, NavigationParameters navParams = null, bool removeMe = false)
where TMultiPageViewModel : ViewModelBase where TMultiPageViewModelChild : ViewModelBase
{
var navPath = typeof(TMultiPageViewModel).Name.TabbedPageKey(typeof(TMultiPageViewModelChild).Name)
.AndRemovePreviousIf(removeMe);
return navService.NavigateAsync(navPath, navParams);
}
#endregion
#region String Extensions
/// <summary>
/// Returns a path that tells Prism to navigate to and treat the corresponding Page (or ViewModel's Page) as a NavigationPage such that it (and every subsequent page in the navigation flow) will be embedded in a NavigationPage.
/// </summary>
/// <param name="pageName">The nameof the Page or the ViewModel</param>
/// <returns>Navigation path string</returns>
public static string NavPageKey(this string pageName)
{
return $"NavigationPage/{pageName.RemoveVmFromStr()}/";
}
/// <summary>
/// Returns a path that tells Prism to navigate to the corresponding Page (or ViewModel's Page)
/// </summary>
/// <param name="pageName">The nameof the Page or the ViewModel</param>
/// <param name="asModal">Whether to add the page as a modal and add it to the modal stack</param>
/// <returns>Navigation path string</returns>
public static string PageKey(this string pageName, bool asModal = false)
{
return $"{pageName.RemoveVmFromStr()}?{KnownNavigationParameters.UseModalNavigation}={asModal}/";
}
/// <summary>
/// Returns a path that tells Prism to navigate to the corresponding Page (or ViewModel's Page) within a TabbedPage OR CarouselPage.
/// Note this should only be used for initial navigation to a Tabbed/CarouselPage and NOT for intra-tab navigation (which would result in another instance of the parent Tabbed/CarouselPage).
/// </summary>
/// <param name="tabbedPageName">The nameof the TabbedPage or its ViewModel</param>
/// <param name="selectTab"> The nameof the Page (or its ViewModel) that is a tab in the Tabbed/CarouselPage to make active</param>
/// <param name="createTabs"> The nameofs the Pages (or their ViewModels) to add as tabs to the TabbedPage (does NOT work with CarouselPage)</param>
/// <returns>Navigation path string</returns>
public static string TabbedPageKey(this string tabbedPageName, string selectTab = "", params string[] createTabs)
{
var selectedTab = selectTab == "" ? "" : $"{KnownNavigationParameters.SelectedTab}={selectTab.RemoveVmFromStr()}";
var createdTabs = createTabs.Length == 0 ? "" : createTabs.Aggregate("", (l, r) => l + $"&{KnownNavigationParameters.CreateTab}={r.RemoveVmFromStr()}");
var queryStr = "?" + (selectedTab != "" ? selectedTab + createdTabs : (createdTabs != "" ? createdTabs.Substring(1) : ""));
return tabbedPageName.RemoveVmFromStr() + (queryStr == "?" ? "" : queryStr) + "/";
}
/// <summary>
/// Wraps a page in a TabbedPage in a NavigationPage
/// </summary>
/// <param name="tabName">The name of the page in the TabbedPage to wrap</param>
/// <returns>NavigationPage-wrapped TabPage name</returns>
public static string WrapTabInNavPage(this string tabName)
{
return $"NavigationPage|{tabName}";
}
/// <summary>
/// Use this when you want to add more pages to the stack than just the one you're navigating to.
/// Note that their navigation hooks would get hit in reverse order (i.e. final destination hits first) any parameters would get passed in to each
/// </summary>
/// <param name="navPath">The current navigation path string being built</param>
/// <param name="pageKey">The the nameof the page (must be ready to go)</param>
/// <returns>Navigation path string</returns>
public static string AndThen(this string navPath, string pageKey)
{
return navPath + pageKey;
}
/// <summary>
/// This feature allows you to remove pages from the navigation stack of a NavigationPage while at the same time navigating to a new page.
/// Chain this command for each page you wish to remove. E.g. 2 of these in a row would remove the current AND previous view and go to navPath.
/// </summary>
/// <param name="navPath">The current navigation path string being built</param>
/// <param name="remove">Whether or not to actually remove the page</param>
/// <returns>Navigation path string</returns>
public static string AndRemovePreviousIf(this string navPath, bool remove = true)
{
if (!remove) return navPath;
return "../" + navPath;
}
/// <summary>
/// Removes "ViewModel" from the string
/// </summary>
/// <param name="name">The string to remove "ViewModel" from</param>
/// <returns>string without "ViewModel" in it</returns>
public static string RemoveVmFromStr(this string name)
{
return name.Replace("ViewModel", "");
}
#endregion
}
public class ViewModelBase : BindableBase, INavigationAware, IDestructible, IConfirmNavigation
{
#region Services
protected INavigationService NavigationService { get; }
protected IDialogService DialogService { get; }
#endregion
#region Properties
private string _title;
public string Title
{
get => _title;
set => SetProperty(ref _title, value);
}
private bool _isBusy;
public bool IsBusy
{
get => _isBusy;
set => SetProperty(ref _isBusy, value);
}
#endregion
public ViewModelBase(INavigationService navigationService)
{
NavigationService = navigationService;
DialogService = App.ContainerInstance.Resolve<IDialogService>();
}
public virtual void OnNavigatingTo(INavigationParameters parameters)
{
// Called when the Page is BEING navigated to.
// NOT called when using device hardware or software back buttons.
// For a tabbed page, will be invoked on the TabbedPage and ALL TABS.
}
public virtual void OnNavigatedTo(INavigationParameters parameters)
{
// Called when the Page is navigated to.
// For a tabbed page, will be invoked on the TabbedPage and ONLY the SELECTED TAB.
}
public virtual bool CanNavigate(INavigationParameters parameters)
{
// Called when NavigateAsync is to check if able to proceed to navigate away from current view
return true;
}
public virtual void OnNavigatedFrom(INavigationParameters parameters)
{
// Called when the Page is navigated away from (including when using device hardware or software back buttons).
// For a tabbed page, will be invoked on the TabbedPage and ONLY the SELECTED TAB.
}
public virtual void Destroy()
{
// Called when the Page/ViewModel is destroyed.
// Do any cleanup here, e.g. unsubscribing from events, removing event handlers, etc.
}
}
@mzhukovs
Copy link
Author

mzhukovs commented Mar 21, 2018

I just started using Prism for Xamarin Forms, which is awesome (even the way the templating is set up) and wanted to learn the ins and outs of how its navigation service. In doing so, I took some notes and added a helper class, as I prefer to use ViewModel types to navigate as opposed to magic strings, and am sharing in case it may be of help/use to anybody else. All I did was set up a few extensions to handle the some basic cases, and for more complex cases you can use the string extensions directly but using nameof so not as pretty. Other than that, plenty of comments.

Some examples:

Navigate to the page associated with the given ViewModel:
NavigationService.NavAsync<TestPageViewModel>();

Navigate to the page associated with the given ViewModel as a modal page, while also removing the current page from the stack:
NavigationService.NavAsync<TestPageViewModel>(asModal: true, removeMe: true);

Navigate to the following 2 pages (and read the summary for the AndThen extension regarding the navigation hooks), passing some parameters into each:

var navParams = new NavigationParameters
{
    { "PathFromWhenceYouCame", NavigationService.GetNavigationUriPath() }
};
NavigationService.NavAsync<TestPageOneViewModel, TestPageTwoViewModel>(navParams);

Navigate to a specific page besides the first within a TabbedPage (works with CarouselPage too):
NavigationService.NavToMultiPageAsync<TabbedPageViewModel, TabViewModel>();

You can use the string extensions there directly as well, coupled with nameof to avoid magic strings, e.g. in your App.OnInitialized you may want to kick things off with a NavigationPage:
NavigationService.NavigateAsync(nameof(MainPageViewModel).NavPageKey());

Or for more advanced cases (this gets a bit ugly, admittedly, but you could just add some more of your own INavigationService extensions to accomodate), such as navigating to a tabbed page, selecting a tab, and dynamically adding a new tab (can add as many as you want):

NavigationService.NavigateAsync(nameof(TabbedPageViewModel).TabbedPageKey(
    selectTab: nameof(TabTwoViewModel),
    createTabs: nameof(NewTabViewModel)));

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment