//Slight tweak on the above method to make it async and allow use with a serviceProvider instead of a host, and with a type argument instead of generic argument so it can be called at run time //The above methos only needs host for it's services so it could be refactored. //The above method only needs <t> so it can be used as a type in an overload of the method it calls. //This version also allows an optional set of parameters //The only downside is you can't have design/compiletime type safety //There's a lot of duplicate code between the two, can probably refactor the core of the method into a separate method that they both call public static async Task <IComponent> AddComponent(this IServiceProvider services, XF.Element parent, Type type, System.Collections.Generic.Dictionary <string, object> parameters = null) { if (services is null) { throw new ArgumentNullException(nameof(services)); } if (parent is null) { throw new ArgumentNullException(nameof(parent)); } if (type is null) { throw new ArgumentNullException(nameof(type)); } if (!typeof(IComponent).IsAssignableFrom(type)) { throw new InvalidOperationException($"Cannot add {type.Name} to {parent.GetType().Name}. {type.Name} is not an IComponent. If you are trying to add a Xamarin.Forms type, try adding the Mobile Blazor Bindings equivalent instead."); } #pragma warning disable CA2000 // Dispose objects before losing scope var renderer = new MobileBlazorBindingsRenderer(services, services.GetRequiredService <ILoggerFactory>()); #pragma warning restore CA2000 // Dispose objects before losing scope return(await renderer.AddComponent(type, CreateHandler(parent, renderer), parameters).ConfigureAwait(false)); }
internal async Task <XF.Page> BuildPage(Type componentType) { var container = new RootContainerHandler(); var route = NavigationParameters[componentType]; #pragma warning disable CA2000 // Dispose objects before losing scope. Renderer is disposed when page is closed. var renderer = new MobileBlazorBindingsRenderer(_services, _services.GetRequiredService <ILoggerFactory>()); #pragma warning restore CA2000 // Dispose objects before losing scope var addComponentTask = renderer.AddComponent(componentType, container, route.Parameters); var elementAddedTask = container.WaitForElementAsync(); await Task.WhenAny(addComponentTask, elementAddedTask).ConfigureAwait(false); if (container.Elements.Count != 1) { throw new InvalidOperationException("The target component of a Shell navigation must have exactly one root element."); } var page = container.Elements.FirstOrDefault() as XF.Page ?? throw new InvalidOperationException("The target component of a Shell navigation must derive from the Page component."); DisposeRendererWhenPageIsClosed(renderer, page); return(page); }
private static ElementHandler CreateHandler(XF.Element parent, MobileBlazorBindingsRenderer renderer) { return(parent switch { XF.ContentPage contentPage => new ContentPageHandler(renderer, contentPage), XF.ContentView contentView => new ContentViewHandler(renderer, contentView), XF.Label label => new LabelHandler(renderer, label), XF.FlyoutPage flyoutPage => new FlyoutPageHandler(renderer, flyoutPage), XF.ScrollView scrollView => new ScrollViewHandler(renderer, scrollView), XF.ShellContent shellContent => new ShellContentHandler(renderer, shellContent), XF.Shell shell => new ShellHandler(renderer, shell), XF.ShellItem shellItem => new ShellItemHandler(renderer, shellItem), XF.ShellSection shellSection => new ShellSectionHandler(renderer, shellSection), XF.TabbedPage tabbedPage => new TabbedPageHandler(renderer, tabbedPage), _ => new ElementHandler(renderer, parent), });
/// <summary> /// Creates a component of type <typeparamref name="TComponent"/> and adds it as a child of <paramref name="parent"/>. /// </summary> /// <typeparam name="TComponent"></typeparam> /// <param name="host"></param> /// <param name="parent"></param> public static void AddComponent <TComponent>(this IHost host, XF.Element parent) where TComponent : IComponent { if (host is null) { throw new ArgumentNullException(nameof(host)); } if (parent is null) { throw new ArgumentNullException(nameof(parent)); } var services = host.Services; var renderer = new MobileBlazorBindingsRenderer(services, services.GetRequiredService <ILoggerFactory>()); // TODO: This call is an async call, but is called as "fire-and-forget," which is not ideal. // We need to figure out how to get Xamarin.Forms to run this startup code asynchronously, which // is how this method should be called. renderer.AddComponent <TComponent>(new ElementHandler(renderer, parent)).ConfigureAwait(false); }
private void DisposeRendererWhenPageIsClosed(MobileBlazorBindingsRenderer renderer, XF.Page page) { // Unfortunately, XF does not expose any Destroyed event for elements. // Therefore we subscribe to Navigated event, and consider page as destroyed // if it is not present in the navigation stack. XF.Shell.Current.Navigated += DisposeWhenNavigatedAway; void DisposeWhenNavigatedAway(object sender, XF.ShellNavigatedEventArgs args) { // We need to check all navigationStacks for all Shell items. var currentPages = XF.Shell.Current.Items .SelectMany(i => i.Items) .SelectMany(i => i.Navigation.NavigationStack); if (!currentPages.Contains(page)) { XF.Shell.Current.Navigated -= DisposeWhenNavigatedAway; renderer.Dispose(); } } }