Skip to content
This repository has been archived by the owner on Feb 16, 2023. It is now read-only.

Navigation Updates #79

Merged
merged 12 commits into from
Jun 19, 2022
Prev Previous commit
Next Next commit
add more context rich exceptions around failed navigation
  • Loading branch information
dansiegel committed Jun 19, 2022
commit 32b729ebfe75802c90dcb7875b18a85212c764a3
22 changes: 22 additions & 0 deletions src/Prism.Maui/Mvvm/ViewCreationException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using Prism.Common;

namespace Prism.Mvvm;

public class ViewCreationException : Exception
{
public ViewCreationException(string viewName, ViewType viewType)
: this(viewName, viewType, null)
{
}

public ViewCreationException(string viewName, ViewType viewType, Exception innerException)
: base($"Unable to create {viewType} '{viewName}'.", innerException)
{
ViewName = viewName;
ViewType = viewType;
}

public ViewType ViewType { get; }

public string ViewName { get; }
}
18 changes: 18 additions & 0 deletions src/Prism.Maui/Mvvm/ViewModelCreationException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace Prism.Mvvm;

public class ViewModelCreationException : Exception
{
public ViewModelCreationException(object view, Exception innerException)
: base($"Unable to Create ViewModel for '{view.GetType().FullName}'.", innerException)
{
if (view is VisualElement visualElement)
{
View = visualElement;
ViewName = (string)visualElement.GetValue(ViewModelLocator.NavigationNameProperty);
}
}

public string ViewName { get; }

public VisualElement View { get; }
}
7 changes: 6 additions & 1 deletion src/Prism.Maui/Mvvm/ViewRegistryBase.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Text.RegularExpressions;
using System.Xml.Linq;
using Prism.Common;
using Prism.Ioc;
using Prism.Navigation.Xaml;
@@ -47,9 +48,13 @@ public object CreateView(IContainerProvider container, string name)
{
throw;
}
catch (ViewModelCreationException)
{
throw;
}
catch (Exception ex)
{
throw new Exception($"Unable to create {_registryType} '{name}'.", ex);
throw new ViewCreationException(name, _registryType, ex);
}
}

18 changes: 11 additions & 7 deletions src/Prism.Maui/Navigation/NavigationException.cs
Original file line number Diff line number Diff line change
@@ -9,10 +9,14 @@ public class NavigationException : Exception
public const string IConfirmNavigationReturnedFalse = "IConfirmNavigation returned false";
public const string NoPageIsRegistered = "No Page has been registered with the provided key";
public const string ErrorCreatingPage = "An error occurred while resolving the page. This is most likely the result of invalid XAML or other type initialization exception";
public const string UnsupportedMauiCreation = "An unsupported Maui Exception occurred. This may be due to a bug with MAUI or something that is otherwise not supported by MAUI.";
public const string UnsupportedMauiNavigation = "An unsupported event occurred while Navigating. The attempted Navigation Stack is not supported by .NET MAUI";
public const string ErrorCreatingViewModel = "A dependency issue occurred while resolving the ViewModel. Check the InnerException for the ContainerResolutionException";
public const string MvvmPatternBreak = "You have referenced a View type and are likely breaking the MVVM pattern. You should never reference a View type from a ViewModel.";
public const string UnknownException = "An unknown error occurred. You may need to specify whether to Use Modal Navigation or not.";

public NavigationException()
: this(UnknownException)
{
}

@@ -21,8 +25,8 @@ public NavigationException(string message)
{
}

public NavigationException(string message, Page page)
: this(message, page, null)
public NavigationException(string message, VisualElement view)
: this(message, view, null)
{
}

@@ -36,18 +40,18 @@ public NavigationException(string message, string navigationKey, Exception inner
{
}

public NavigationException(string message, Page page, Exception innerException)
: this(message, null, page, innerException)
public NavigationException(string message, VisualElement view, Exception innerException)
: this(message, null, view, innerException)
{
}

public NavigationException(string message, string navigationKey, Page page, Exception innerException) : base(message, innerException)
public NavigationException(string message, string navigationKey, VisualElement view, Exception innerException) : base(message, innerException)
{
Page = page;
View = view;
NavigationKey = navigationKey;
}

public Page Page { get; }
public VisualElement View { get; }

public string NavigationKey { get; }
}
120 changes: 71 additions & 49 deletions src/Prism.Maui/Navigation/PageNavigationService.cs
Original file line number Diff line number Diff line change
@@ -708,14 +708,32 @@ protected virtual Page CreatePage(string segmentName)

return page;
}
catch(NavigationException)
{
throw;
}
catch(KeyNotFoundException knfe)
{
throw new NavigationException(NavigationException.NoPageIsRegistered, segmentName, knfe);
}
catch(ViewModelCreationException vmce)
{
throw new NavigationException(NavigationException.ErrorCreatingViewModel, segmentName, _pageAccessor.Page, vmce);
}
//catch(ViewCreationException viewCreationException)
//{
// if(!string.IsNullOrEmpty(viewCreationException.InnerException?.Message) && viewCreationException.InnerException.Message.Contains("Maui"))
// throw new NavigationException(NavigationException.)
//}
catch (Exception ex)
{
if (ex is NavigationException)
throw;

else if(ex is KeyNotFoundException)
throw new NavigationException(NavigationException.NoPageIsRegistered, segmentName, ex);

var inner = ex.InnerException;
while(inner is not null)
{
if (inner.Message.Contains("thread with a dispatcher"))
throw new NavigationException(NavigationException.UnsupportedMauiCreation, segmentName, _pageAccessor.Page, ex);
inner = inner.InnerException;
}
throw new NavigationException(NavigationException.ErrorCreatingPage, segmentName, ex);
}
}
@@ -727,7 +745,7 @@ protected virtual Page CreatePageFromSegment(string segment)
if (page is null)
{
var innerException = new NullReferenceException(string.Format("{0} could not be created. Please make sure you have registered {0} for navigation.", segmentName));
throw new NavigationException(NavigationException.NoPageIsRegistered, _pageAccessor.Page, innerException);
throw new NavigationException(NavigationException.NoPageIsRegistered, segmentName, _pageAccessor.Page, innerException);
}

return page;
@@ -937,71 +955,75 @@ await DoNavigateAction(onNavigatedFromTarget, segment, nextPage, parameters, asy
await ProcessNavigation(currentPage.Navigation.NavigationStack.Last(), illegalSegments, parameters, true, animated);
}

protected virtual Task DoPush(Page currentPage, Page page, bool? useModalNavigation, bool animated, bool insertBeforeLast = false, int navigationOffset = 0)
protected virtual async Task DoPush(Page currentPage, Page page, bool? useModalNavigation, bool animated, bool insertBeforeLast = false, int navigationOffset = 0)
{
if (page is null)
throw new ArgumentNullException(nameof(page));

// Prevent Page from using Parent's ViewModel
if (page.BindingContext is null)
page.BindingContext = new object();

if (currentPage is null)
try
{
if (_application.Windows.OfType<PrismWindow>().Any(x => x.Name == PrismWindow.DefaultWindowName))
_window = _application.Windows.OfType<PrismWindow>().First(x => x.Name == PrismWindow.DefaultWindowName);
// Prevent Page from using Parent's ViewModel
if (page.BindingContext is null)
page.BindingContext = new object();

if (Window is null)
if (currentPage is null)
{
_window = new PrismWindow
if (_application.Windows.OfType<PrismWindow>().Any(x => x.Name == PrismWindow.DefaultWindowName))
_window = _application.Windows.OfType<PrismWindow>().First(x => x.Name == PrismWindow.DefaultWindowName);

if (Window is null)
{
_window = new PrismWindow
{
Page = page
};
((List<Window>)_application.Windows).Add(_window as PrismWindow);
}
else
{
Page = page
};
((List<Window>)_application.Windows).Add(_window as PrismWindow);
}
else
{
#if !ANDROID
// BUG: https://github.com/dotnet/maui/issues/7275
Window.Page = page;
// BUG: https://github.com/dotnet/maui/issues/7275
Window.Page = page;
#if WINDOWS
page.ForceLayout();
page.ForceLayout();
#endif
#else

// HACK: This is the only way CURRENTLY to ensure that the UI resets for Absolute Navigation
var newWindow = new PrismWindow
{
Page = page
};
_application.OpenWindow(newWindow);
_application.CloseWindow(Window);
_window = null;
// HACK: This is the only way CURRENTLY to ensure that the UI resets for Absolute Navigation
var newWindow = new PrismWindow
{
Page = page
};
_application.OpenWindow(newWindow);
_application.CloseWindow(Window);
_window = null;
#endif
}

return Task.FromResult<object>(null);
}
else
{
bool useModalForPush = UseModalNavigation(currentPage, useModalNavigation);

if (useModalForPush)
{
return currentPage.Navigation.PushModalAsync(page, animated);
}
}
else
{
if (insertBeforeLast)
bool useModalForPush = UseModalNavigation(currentPage, useModalNavigation);

if (useModalForPush)
{
return InsertPageBefore(currentPage, page, navigationOffset);
await currentPage.Navigation.PushModalAsync(page, animated);
}
else
{
return currentPage.Navigation.PushAsync(page, animated);
if (insertBeforeLast)
{
await InsertPageBefore(currentPage, page, navigationOffset);
}
else
{
await currentPage.Navigation.PushAsync(page, animated);
}
}
}
}
catch (Exception ex)
{
throw new NavigationException(NavigationException.UnsupportedMauiNavigation, _pageAccessor.Page, ex);
}
}

protected virtual Task InsertPageBefore(Page currentPage, Page page, int pageOffset)
15 changes: 11 additions & 4 deletions src/Prism.Maui/PrismAppBuilder.cs
Original file line number Diff line number Diff line change
@@ -72,12 +72,19 @@ private void ConfigureViewModelLocator(IContainerProvider container)

internal static object DefaultViewModelLocator(object view, Type viewModelType)
{
if (view is not BindableObject bindable)
return null;
try
{
if (view is not BindableObject bindable)
return null;

var container = bindable.GetContainerProvider();
var container = bindable.GetContainerProvider();

return container.Resolve(viewModelType);
return container.Resolve(viewModelType);
}
catch (Exception ex)
{
throw new ViewModelCreationException(view, ex);
}
}

public PrismAppBuilder RegisterTypes(Action<IContainerRegistry> registerTypes)