From 3784e2d39a309919d71446b21504075b4c3830fd Mon Sep 17 00:00:00 2001 From: Dan Siegel Date: Sat, 6 Jan 2024 16:30:49 -0600 Subject: [PATCH 1/3] chore: refactoring/code cleanup --- src/Maui/Prism.Maui/Mvvm/ViewModelLocator.cs | 10 ++++++-- src/Maui/Prism.Maui/Mvvm/ViewRegistryBase.cs | 11 +++++++-- .../Prism.Maui/Navigation/Xaml/Navigation.cs | 24 ++++++++++++++++--- src/Maui/Prism.Maui/PrismAppBuilder.cs | 8 +++++-- .../Mvvm/ViewRegistryBase{TBaseView}.cs | 8 +------ .../Fixtures/TestBase.cs | 9 ++++--- 6 files changed, 49 insertions(+), 21 deletions(-) diff --git a/src/Maui/Prism.Maui/Mvvm/ViewModelLocator.cs b/src/Maui/Prism.Maui/Mvvm/ViewModelLocator.cs index c715392496..57cc71f72d 100644 --- a/src/Maui/Prism.Maui/Mvvm/ViewModelLocator.cs +++ b/src/Maui/Prism.Maui/Mvvm/ViewModelLocator.cs @@ -1,7 +1,7 @@ namespace Prism.Mvvm; /// -/// This class defines the attached property and related change handler that calls the . +/// This class defines the attached property and related change handler that calls the . /// public static class ViewModelLocator { @@ -60,9 +60,11 @@ internal static void Autowire(object view) if (view is Element element && ((ViewModelLocatorBehavior)element.GetValue(AutowireViewModelProperty) == ViewModelLocatorBehavior.Disabled || (element.BindingContext is not null && element.BindingContext != element.Parent))) + { return; + } - else if(view is TabbedPage tabbed) + if (view is TabbedPage tabbed) { foreach (var child in tabbed.Children) Autowire(child); @@ -75,7 +77,9 @@ internal static void Autowire(object view) ViewModelLocationProvider.AutoWireViewModelChanged(view, Bind); if (view is BindableObject bindable && bindable.BindingContext is null) + { bindable.BindingContext = new object(); + } } /// @@ -86,6 +90,8 @@ internal static void Autowire(object view) private static void Bind(object view, object viewModel) { if (view is BindableObject element) + { element.BindingContext = viewModel; + } } } diff --git a/src/Maui/Prism.Maui/Mvvm/ViewRegistryBase.cs b/src/Maui/Prism.Maui/Mvvm/ViewRegistryBase.cs index 0f565699eb..cbfcf3c06c 100644 --- a/src/Maui/Prism.Maui/Mvvm/ViewRegistryBase.cs +++ b/src/Maui/Prism.Maui/Mvvm/ViewRegistryBase.cs @@ -1,10 +1,17 @@ -using Prism.Ioc; -using Prism.Navigation.Xaml; +using Prism.Navigation.Xaml; namespace Prism.Mvvm; +/// +/// The Base class for .NET Maui's ViewModel Registry +/// public abstract class ViewRegistryBase : ViewRegistryBase { + /// + /// Initializes a new instance of the + /// + /// The Registry Type + /// The ViewRegistration collection protected ViewRegistryBase(ViewType registryType, IEnumerable registrations) : base(registryType, registrations) { diff --git a/src/Maui/Prism.Maui/Navigation/Xaml/Navigation.cs b/src/Maui/Prism.Maui/Navigation/Xaml/Navigation.cs index a59f7e1f27..4952349be1 100644 --- a/src/Maui/Prism.Maui/Navigation/Xaml/Navigation.cs +++ b/src/Maui/Prism.Maui/Navigation/Xaml/Navigation.cs @@ -1,6 +1,5 @@ using System.ComponentModel; using Prism.Common; -using Prism.Ioc; using Prism.Navigation.Internals; namespace Prism.Navigation.Xaml; @@ -79,13 +78,19 @@ private static void OnNavigationScopeChanged(BindableObject bindable, object old /// The Can Navigate value public static void SetCanNavigate(BindableObject view, bool value) => view.SetValue(CanNavigateProperty, value); + /// + /// Gets the Child Regions for a given + /// + /// The host. + /// Initializes the if it has not been set. + /// The . [EditorBrowsable(EditorBrowsableState.Never)] public static ChildRegionCollection GetChildRegions(this Page page, bool setIfNull = false) { var value = page.GetValue(ChildMvvmViewsProperty) as ChildRegionCollection; if (value is null && setIfNull) { - value = new ChildRegionCollection(); + value = []; page.SetValue(ChildMvvmViewsProperty, value); } @@ -111,18 +116,31 @@ internal static void ClearChildRegions(this Page page) [EditorBrowsable(EditorBrowsableState.Never)] public static INavigationService GetNavigationService(Page page) { - if (page == null) throw new ArgumentNullException(nameof(page)); + ArgumentNullException.ThrowIfNull(page); var container = page.GetContainerProvider(); return container.Resolve(); } + /// + /// Sets the for the given + /// + /// The . + /// The . [EditorBrowsable(EditorBrowsableState.Never)] public static void SetContainerProvider(this BindableObject bindable, IContainerProvider container) { bindable.SetValue(NavigationScopeProperty, container); } + /// + /// Gets the Container for the given View + /// + /// The View + /// The . + /// + /// Will initialize a new Container Scope if the is Forced. + /// [EditorBrowsable(EditorBrowsableState.Never)] public static IContainerProvider GetContainerProvider(this BindableObject bindable) { diff --git a/src/Maui/Prism.Maui/PrismAppBuilder.cs b/src/Maui/Prism.Maui/PrismAppBuilder.cs index 3432ed2af2..8e0b33bfec 100644 --- a/src/Maui/Prism.Maui/PrismAppBuilder.cs +++ b/src/Maui/Prism.Maui/PrismAppBuilder.cs @@ -106,12 +106,16 @@ internal static object DefaultViewModelLocator(object view, Type viewModelType) { try { - if (view is not BindableObject bindable) + if (view is not BindableObject bindable || bindable.BindingContext is not null) return null; var container = bindable.GetContainerProvider(); - return container.Resolve(viewModelType); + return container.Resolve(viewModelType, (typeof(IDispatcher), bindable.Dispatcher)); + } + catch (ViewModelCreationException) + { + throw; } catch (Exception ex) { diff --git a/src/Prism.Core/Mvvm/ViewRegistryBase{TBaseView}.cs b/src/Prism.Core/Mvvm/ViewRegistryBase{TBaseView}.cs index d5872923ea..0e144013c1 100644 --- a/src/Prism.Core/Mvvm/ViewRegistryBase{TBaseView}.cs +++ b/src/Prism.Core/Mvvm/ViewRegistryBase{TBaseView}.cs @@ -28,21 +28,15 @@ public object CreateView(IContainerProvider container, string name) { try { - var registration = GetRegistration(name); - if (registration is null) - throw new KeyNotFoundException($"No view with the name '{name}' has been registered"); - + var registration = GetRegistration(name) ?? throw new KeyNotFoundException($"No view with the name '{name}' has been registered"); var view = container.Resolve(registration.View) as TBaseView; SetNavigationNameProperty(view, registration.Name); - //; - //; SetContainerProvider(view, container); ConfigureView(view, container); if (registration.ViewModel is not null) SetViewModelProperty(view, registration.ViewModel); - // Autowire(view); diff --git a/tests/Maui/Prism.DryIoc.Maui.Tests/Fixtures/TestBase.cs b/tests/Maui/Prism.DryIoc.Maui.Tests/Fixtures/TestBase.cs index b04ac2e9e6..c17efd795f 100644 --- a/tests/Maui/Prism.DryIoc.Maui.Tests/Fixtures/TestBase.cs +++ b/tests/Maui/Prism.DryIoc.Maui.Tests/Fixtures/TestBase.cs @@ -1,9 +1,8 @@ -using Prism.DryIoc.Maui.Tests.Mocks.ViewModels; -using Prism.DryIoc.Maui.Tests.Mocks.Views; using Microsoft.Extensions.Logging; -using Prism.DryIoc.Maui.Tests.Mocks.Logging; -using Microsoft.Maui.Dispatching; using Prism.DryIoc.Maui.Tests.Mocks; +using Prism.DryIoc.Maui.Tests.Mocks.Logging; +using Prism.DryIoc.Maui.Tests.Mocks.ViewModels; +using Prism.DryIoc.Maui.Tests.Mocks.Views; namespace Prism.DryIoc.Maui.Tests.Fixtures; @@ -64,7 +63,7 @@ protected MauiAppBuilder CreateBuilder(Action configurePrism) }); } - protected Window GetWindow(MauiApp mauiApp) + protected static Window GetWindow(MauiApp mauiApp) { var app = mauiApp.Services.GetService(); Assert.NotNull(app); From 15b366a943d1e070454289d911fd8a14b305891f Mon Sep 17 00:00:00 2001 From: Dan Siegel Date: Sat, 6 Jan 2024 16:31:00 -0600 Subject: [PATCH 2/3] test: adding more tests for Region Navigation --- .../Fixtures/Regions/RegionFixture.cs | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/tests/Maui/Prism.DryIoc.Maui.Tests/Fixtures/Regions/RegionFixture.cs b/tests/Maui/Prism.DryIoc.Maui.Tests/Fixtures/Regions/RegionFixture.cs index f2fbea3d8a..fe910d4e00 100644 --- a/tests/Maui/Prism.DryIoc.Maui.Tests/Fixtures/Regions/RegionFixture.cs +++ b/tests/Maui/Prism.DryIoc.Maui.Tests/Fixtures/Regions/RegionFixture.cs @@ -173,4 +173,55 @@ public void RegionWithDefaultView_IsAutoPopulated() Assert.NotNull(region.Content); Assert.IsType(region.Content); } + + [Fact] + public async Task Region_IsDestroyed_OnNavigatedAway() + { + var mauiApp = CreateBuilder(prism => prism + .RegisterTypes(container => + { + container.RegisterForNavigation("MainPage"); + container.RegisterForRegionNavigation(); + }) + .CreateWindow("MainPage")) + .Build(); + + var window = GetWindow(mauiApp); + + var navigationService = Prism.Navigation.Xaml.Navigation.GetNavigationService(window.Page); + var regionManager = mauiApp.Services.GetRequiredService(); + + Assert.Single(regionManager.Regions); + await navigationService.NavigateAsync("/MockViewA"); + Assert.Empty(regionManager.Regions); + } + + [Fact] + public async Task Region_IsDestroyed_OnNavigationGoBack() + { + var mauiApp = CreateBuilder(prism => prism + .RegisterTypes(container => + { + container.RegisterForNavigation("RegionPage"); + container.RegisterForRegionNavigation(); + }) + .CreateWindow("NavigationPage/MockViewA")) + .Build(); + + var window = GetWindow(mauiApp); + var navPage = window.Page as NavigationPage; + + var navigationService = Prism.Navigation.Xaml.Navigation.GetNavigationService(navPage.RootPage); + var regionManager = mauiApp.Services.GetRequiredService(); + + Assert.Empty(regionManager.Regions); + await navigationService.NavigateAsync("RegionPage"); + Assert.Single(regionManager.Regions); + + await Prism.Navigation.Xaml.Navigation.GetNavigationService(navPage.CurrentPage).GoBackAsync(); + Assert.Empty(regionManager.Regions); + + var result = await navigationService.NavigateAsync("RegionPage"); + Assert.True(result.Success); + } } From 23452b5530a9fcd562b502513a30d4a57d7165e4 Mon Sep 17 00:00:00 2001 From: Dan Siegel Date: Sat, 6 Jan 2024 16:47:38 -0600 Subject: [PATCH 3/3] chore: adding xml docs --- .../Navigation/PrismWindowManager.cs | 2 +- src/Maui/Prism.Maui/PrismAppBuilder.cs | 16 ++-- .../Prism.Maui/PrismAppBuilderExtensions.cs | 95 +++++++++++++++++-- 3 files changed, 96 insertions(+), 17 deletions(-) diff --git a/src/Maui/Prism.Maui/Navigation/PrismWindowManager.cs b/src/Maui/Prism.Maui/Navigation/PrismWindowManager.cs index 8c2390cf8b..7b95b7a770 100644 --- a/src/Maui/Prism.Maui/Navigation/PrismWindowManager.cs +++ b/src/Maui/Prism.Maui/Navigation/PrismWindowManager.cs @@ -20,7 +20,7 @@ public Window CreateWindow(Application app, IActivationState activationState) else if (app.Windows.OfType().Any()) return _initialWindow = app.Windows.OfType().First(); - activationState.Context.Services.GetRequiredService().OnAppStarted(); + activationState.Context.Services.GetRequiredService().OnCreateWindow(); return _initialWindow ?? throw new InvalidNavigationException("Expected Navigation Failed. No Root Window has been created."); } diff --git a/src/Maui/Prism.Maui/PrismAppBuilder.cs b/src/Maui/Prism.Maui/PrismAppBuilder.cs index 8e0b33bfec..1dcb36ddc9 100644 --- a/src/Maui/Prism.Maui/PrismAppBuilder.cs +++ b/src/Maui/Prism.Maui/PrismAppBuilder.cs @@ -21,7 +21,7 @@ public sealed class PrismAppBuilder private List> _registrations { get; } private List> _initializations { get; } private IContainerProvider _container { get; } - private Func _onAppStarted; + private Func _createWindow; private Action _configureAdapters; private Action _configureBehaviors; @@ -181,14 +181,14 @@ internal void OnInitialized() } } - internal void OnAppStarted() + internal void OnCreateWindow() { - if (_onAppStarted is null) - throw new ArgumentException("You must call OnAppStart on the PrismAppBuilder."); + if (_createWindow is null) + throw new ArgumentException("You must call CreateWindow on the PrismAppBuilder."); // Ensure that this is executed before we navigate. OnInitialized(); - var onStart = _onAppStarted(_container, _container.Resolve()); + var onStart = _createWindow(_container, _container.Resolve()); onStart.Wait(); } @@ -196,11 +196,11 @@ internal void OnAppStarted() /// When the is started and the native platform calls /// this delegate will be invoked to do your initial Navigation. /// - /// The Navigation Delegate. + /// The Navigation Delegate. /// The . - public PrismAppBuilder CreateWindow(Func onAppStarted) + public PrismAppBuilder CreateWindow(Func createWindow) { - _onAppStarted = onAppStarted; + _createWindow = createWindow; return this; } diff --git a/src/Maui/Prism.Maui/PrismAppBuilderExtensions.cs b/src/Maui/Prism.Maui/PrismAppBuilderExtensions.cs index edaa5be09e..ce59d27eeb 100644 --- a/src/Maui/Prism.Maui/PrismAppBuilderExtensions.cs +++ b/src/Maui/Prism.Maui/PrismAppBuilderExtensions.cs @@ -6,10 +6,20 @@ namespace Prism; +/// +/// Common extensions and overloads for the +/// public static class PrismAppBuilderExtensions { private static bool s_didRegisterModules = false; + /// + /// Configures the to use Prism with a callback for the + /// + /// The . + /// The instance of the Prism should use. + /// A delegate callback for the + /// The . public static MauiAppBuilder UsePrism(this MauiAppBuilder builder, IContainerExtension containerExtension, Action configurePrism) { var prismBuilder = new PrismAppBuilder(containerExtension, builder); @@ -17,6 +27,12 @@ public static MauiAppBuilder UsePrism(this MauiAppBuilder builder, IContainerExt return builder; } + /// + /// Provides a Delegate to invoke when the App is initialized. + /// + /// The . + /// The delegate to invoke. + /// The . public static PrismAppBuilder OnInitialized(this PrismAppBuilder builder, Action action) { return builder.OnInitialized(_ => action()); @@ -45,9 +61,24 @@ public static PrismAppBuilder ConfigureModuleCatalog(this PrismAppBuilder builde }); } + /// + /// When the is started and the native platform calls + /// this delegate will be invoked to do your initial Navigation. + /// + /// The . + /// The initial Navigation Uri. + /// The . public static PrismAppBuilder CreateWindow(this PrismAppBuilder builder, string uri) => builder.CreateWindow(navigation => navigation.NavigateAsync(uri)); + /// + /// When the is started and the native platform calls + /// this delegate will be invoked to do your initial Navigation. + /// + /// The . + /// The intial Navigation Uri. + /// A delegate callback if the navigation fails. + /// The . public static PrismAppBuilder CreateWindow(this PrismAppBuilder builder, string uri, Action onError) => builder.CreateWindow(async navigation => { @@ -56,30 +87,78 @@ public static PrismAppBuilder CreateWindow(this PrismAppBuilder builder, string onError(result.Exception); }); - public static PrismAppBuilder CreateWindow(this PrismAppBuilder builder, Func CreateWindowed) => - builder.CreateWindow((c, n) => CreateWindowed(c, n)); + /// + /// When the is started and the native platform calls + /// this delegate will be invoked to do your initial Navigation. + /// + /// The . + /// The Navigation Delegate. + /// The . + public static PrismAppBuilder CreateWindow(this PrismAppBuilder builder, Func createWindow) => + builder.CreateWindow((c, n) => createWindow(c, n)); - public static PrismAppBuilder CreateWindow(this PrismAppBuilder builder, Func CreateWindowed) => - builder.CreateWindow((_, n) => CreateWindowed(n)); + /// + /// When the is started and the native platform calls + /// this delegate will be invoked to do your initial Navigation. + /// + /// The . + /// The Navigation Delegate. + /// The . + public static PrismAppBuilder CreateWindow(this PrismAppBuilder builder, Func createWindow) => + builder.CreateWindow((_, n) => createWindow(n)); - public static PrismAppBuilder CreateWindow(this PrismAppBuilder builder, Func CreateWindowed) => - builder.CreateWindow(n => CreateWindowed(n).NavigateAsync()); + /// + /// When the is started and the native platform calls + /// this delegate will be invoked to do your initial Navigation. + /// + /// The . + /// The Navigation Delegate. + /// The . + public static PrismAppBuilder CreateWindow(this PrismAppBuilder builder, Func createWindow) => + builder.CreateWindow(n => createWindow(n).NavigateAsync()); + + /// + /// When the is started and the native platform calls + /// this delegate will be invoked to do your initial Navigation. + /// + /// The . + /// The Navigation Delegate. + /// The . + public static PrismAppBuilder CreateWindow(this PrismAppBuilder builder, Func createWindow) => + builder.CreateWindow((c, n) => createWindow(c, n).NavigateAsync()); - public static PrismAppBuilder CreateWindow(this PrismAppBuilder builder, Func CreateWindowed) => - builder.CreateWindow((c, n) => CreateWindowed(c, n).NavigateAsync()); + /// + /// Provides a configuration delegate to add services to the + /// + /// The . + /// Configuration Delegate + /// The . public static PrismAppBuilder ConfigureServices(this PrismAppBuilder builder, Action configureServices) { configureServices(builder.MauiBuilder.Services); return builder; } + /// + /// Provides a delegate to configure Logging within the Maui application + /// + /// The . + /// + /// The . public static PrismAppBuilder ConfigureLogging(this PrismAppBuilder builder, Action configureLogging) { configureLogging(builder.MauiBuilder.Logging); return builder; } + /// + /// Provides a configuration Delegate to the to set the + /// DefaultViewTypeToViewModelTypeResolver. + /// + /// The . + /// The Configuration Delegate for the Default ViewType to ViewModelType Resolver. + /// The . public static PrismAppBuilder ConfigureViewTypeToViewModelTypeResolver(this PrismAppBuilder builder, Func viewModelTypeResolver) { ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver(viewModelTypeResolver);