diff --git a/src/BlazorBindings.Core/NativeComponentRenderer.cs b/src/BlazorBindings.Core/NativeComponentRenderer.cs index 8ba0a4e9..a8254f0b 100644 --- a/src/BlazorBindings.Core/NativeComponentRenderer.cs +++ b/src/BlazorBindings.Core/NativeComponentRenderer.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; namespace BlazorBindings.Core @@ -15,6 +16,7 @@ public abstract class NativeComponentRenderer : Renderer private readonly Dictionary _componentIdToAdapter = new Dictionary(); private ElementManager _elementManager; private readonly Dictionary> _eventRegistrations = new Dictionary>(); + private readonly List<(int Id, IComponent Component)> _rootComponents = new(); public NativeComponentRenderer(IServiceProvider serviceProvider, ILoggerFactory loggerFactory) @@ -63,6 +65,8 @@ public async Task AddComponent(Type componentType, IElementHandler p var component = InstantiateComponent(componentType); var componentId = AssignRootComponentId(component); + _rootComponents.Add((componentId, component)); + var rootAdapter = new NativeComponentAdapter(this, closestPhysicalParent: parent, knownTargetElement: parent) { Name = $"RootAdapter attached to {parent.GetType().FullName}", @@ -75,15 +79,23 @@ public async Task AddComponent(Type componentType, IElementHandler p return component; }).ConfigureAwait(false); } -#pragma warning disable CA1031 // Do not catch general exception types catch (Exception ex) -#pragma warning restore CA1031 // Do not catch general exception types { HandleException(ex); return null; } } + /// + /// Removes the specified component from the renderer, causing the component and its + /// descendants to be disposed. + /// + public void RemoveRootComponent(IComponent component) + { + var componentId = _rootComponents.LastOrDefault(c => c.Component == component).Id; + RemoveRootComponent(componentId); + } + protected override Task UpdateDisplayAsync(in RenderBatch renderBatch) { HashSet processedComponentIds = new HashSet(); diff --git a/src/BlazorBindings.Maui/MauiAppBuilderExtensions.cs b/src/BlazorBindings.Maui/MauiAppBuilderExtensions.cs index e8bee1fc..5c436ff2 100644 --- a/src/BlazorBindings.Maui/MauiAppBuilderExtensions.cs +++ b/src/BlazorBindings.Maui/MauiAppBuilderExtensions.cs @@ -18,7 +18,7 @@ public static MauiAppBuilder UseMauiBlazorBindings(this MauiAppBuilder builder) builder.Services .AddSingleton(svcs => new Navigation(svcs)) .AddSingleton(services => services.GetRequiredService()) - .AddScoped(svcs => new MauiBlazorBindingsRenderer(svcs, svcs.GetRequiredService())); + .AddSingleton(svcs => new MauiBlazorBindingsRenderer(svcs, svcs.GetRequiredService())); return builder; } diff --git a/src/BlazorBindings.Maui/MauiBlazorBindingsRenderer.cs b/src/BlazorBindings.Maui/MauiBlazorBindingsRenderer.cs index 5579cb3f..61c10949 100644 --- a/src/BlazorBindings.Maui/MauiBlazorBindingsRenderer.cs +++ b/src/BlazorBindings.Maui/MauiBlazorBindingsRenderer.cs @@ -40,7 +40,7 @@ async Task AddComponentLocal() if (!elementsComponentTask.IsCompleted && parent is MC.Application app) { - // MAUI requires the Application to have the MainPage. If rendering task is not completed synchroniously, + // MAUI requires the Application to have the MainPage. If rendering task is not completed synchronously, // we need to set MainPage to something. app.MainPage ??= new MC.ContentPage(); } diff --git a/src/BlazorBindings.Maui/Navigation/Navigation.cs b/src/BlazorBindings.Maui/Navigation/Navigation.cs index 2216a366..3f959d83 100644 --- a/src/BlazorBindings.Maui/Navigation/Navigation.cs +++ b/src/BlazorBindings.Maui/Navigation/Navigation.cs @@ -109,23 +109,24 @@ public Task BuildElement(RenderFragment renderFragment) where T : Element [EditorBrowsable(EditorBrowsableState.Never)] public async Task BuildElement(Type componentType, Dictionary arguments) where T : Element { - var scope = _services.CreateScope(); - var serviceProvider = scope.ServiceProvider; - var renderer = serviceProvider.GetRequiredService(); + var renderer = _services.GetRequiredService(); - var element = (Element)(await renderer.GetElementFromRenderedComponent(componentType, arguments)).Element; + var (bindableObject, componentTask) = await renderer.GetElementFromRenderedComponent(componentType, arguments); + var element = (Element)bindableObject; element.ParentChanged += DisposeScopeWhenParentRemoved; return element as T ?? throw new InvalidOperationException($"The target component of a navigation must derive from the {typeof(T).Name} component."); - void DisposeScopeWhenParentRemoved(object _, EventArgs __) + async void DisposeScopeWhenParentRemoved(object _, EventArgs __) { if (element.Parent is null) { - scope.Dispose(); element.ParentChanged -= DisposeScopeWhenParentRemoved; + + var component = await componentTask; + renderer.RemoveRootComponent(component); } } }