The project I am working on contains the following structure:
When app is launched, user sees a Welcome page. At that point user has two options. They can either login or register. If logged in == true; then go to master detail page. Or in registration, if register == success then go to login page and follow the same process and end up in the master detail page.
-> Login Page ||
Welcome Page >> ================== || => MasterDetailPage
-> Register Page -> Login page ||
I am using MVVM Light to handle my navigation stack via INavigationService as my UI and business logic is separated via MVVM. Everything works pretty good except for I need to reset the navigation stack so the user will not be able to access any page before the "MasterDetailPage" showed above. Right now users can go back to login or registration or whatever page they were before, by using the hardware back button on Android or swiping from the left edge on iOS. Plus, There is a navigation back button on top navigation bar anyway.
My App.cs looks something like this
public App()
{
var nav = RegisterNavigationService();
SimpleIoc.Default.Register<INavigationService>(() => nav);
InitializeComponent();
var initialPage = new NavigationPage(new WelcomePage());
nav.Initialize(initialPage);
MainPage = initialPage;
}
private NavigationService RegisterNavigationService()
{
var nav = new NavigationService();
nav.Configure(Locator.LoginForm, typeof(LoginForm));
nav.Configure(Locator.RegisterSuccessPage, typeof(RegisterSuccessPage));
nav.Configure(Locator.RegistrationForm, typeof(RegistrationForm));
nav.Configure(Locator.WelcomePage, typeof(WelcomePage));
nav.Configure(Locator.MasterMainPage, typeof(MasterMainPage));
return nav;
}
On my view models, I handle the navigation commands like this:
public class LoginFormViewModel : BaseViewModel
{
private readonly INavigationService _navigationService;
public Command NavigateToMainPage { get; }
public LoginFormViewModel(INavigationService navigationService)
{
_navigationService = navigationService ?? throw new ArgumentNullException("navigationService");
NavigateToMainPage = new Command(() => NavigateToMainApp());
}
private void NavigateToMainApp()
{
_navigationService.NavigateTo(Locator.MasterMainPage);
}
}
Finally, my NavigationService.cs looks like this... I barely touched this part of the code... The only thing I tried is the 'ClearNavigationStack' method but that was a failure.
public class NavigationService : INavigationService, INavigationServiceExtensions
{
private Dictionary<string, Type> _pagesByKey = new Dictionary<string, Type>();
private NavigationPage _navigation;
public string CurrentPageKey
{
get
{
lock (_pagesByKey)
{
if (_navigation.CurrentPage == null)
{
return null;
}
var pageType = _navigation.CurrentPage.GetType();
return _pagesByKey.ContainsValue(pageType)
? _pagesByKey.First(p => p.Value == pageType).Key
: null;
}
}
}
public void GoBack()
{
_navigation.PopAsync();
}
public void NavigateTo(string pageKey)
{
NavigateTo(pageKey, null);
}
public void NavigateTo(string pageKey, object parameter)
{
lock (_pagesByKey)
{
if (_pagesByKey.ContainsKey(pageKey))
{
ConstructorInfo constructor;
object[] parameters;
var type = _pagesByKey[pageKey];
if (parameter == null)
{
constructor = type.GetTypeInfo()
.DeclaredConstructors
.FirstOrDefault(c => !c.GetParameters().Any());
parameters = new object[] { };
}
else
{
constructor = type.GetTypeInfo()
.DeclaredConstructors
.FirstOrDefault(
c =>
{
var p = c.GetParameters();
return p.Count() == 1
&& p[0].ParameterType == parameter.GetType();
});
parameters = new[] { parameter };
}
if (constructor == null)
{
throw new InvalidOperationException("No suitable constructor found for page " + pageKey);
}
var page = constructor.Invoke(parameters) as Page;
_navigation.PushAsync(page);
}
else
{
throw new ArgumentException(
string.Format("No such page: {0}. Did you forget to call NavigationService.Configure?", pageKey), "pageKey");
}
}
}
public void Configure(string pageKey, Type pageType)
{
lock (_pagesByKey)
{
if (_pagesByKey.ContainsKey(pageKey))
{
_pagesByKey[pageKey] = pageType;
}
else
{
_pagesByKey.Add(pageKey, pageType);
}
}
}
public void ClearNavigationStack()
{
lock (_pagesByKey)
{
foreach (var pageKey in _pagesByKey.Keys)
{
_pagesByKey.Remove(pageKey);
}
}
}
public void Initialize(NavigationPage navigation)
{
_navigation = navigation;
}
}
I've taken this bit from the following git repo: https://github.com/mallibone/MvvmLightNavigation.XamarinForms
by following this tutorial: https://mallibone.com/post/xamarin.forms-navigation-with-mvvm-light
Note: It is a PCL.
Any suggestion is welcome as I've been on this for the last 2 days.
EDIT: Just now, I've managed to "hide" the nav stack by setting my MainPage to something like this
App.Current.MainPage = new MasterMainPage();
But it seems like a code smell and looks like a horrific hack. Plus I am not too sure if it "violates" the concepts I am following... And I guess this navigation stack will never be gone anyway as I will do other navigation stacks inside the master detail pages.