KEMBAR78
Experiments for Native Lifecycle by mattleibow · Pull Request #606 · dotnet/maui · GitHub
Skip to content

Conversation

@mattleibow
Copy link
Member

@mattleibow mattleibow commented Mar 28, 2021

Description of Change

Hehe, OK. Probably went a bit nuts as I do when I am feeling zippy - whatever that means... As I was adding the configuration for essentials, I noticed a pattern. Each time I wanted to add a "feature" chunk, like configuring the handlers, configuring the lifecycle events, configuring essentials I could either take one of two approaches:

  1. Add each step that I did to ConfigureServices.  This is for example when adding the lifecycle, if I did builder.AddAndroid(...).AddAndroid(...) then I would end up with 2 service registrations. Things like essentials also do this to track events and we end up with even more service instances. This is all valid since we can do services.GetServices<T>() and we get a collection back. However, this requires that we use the MS.Ext package, or we implement support for multiple services. Another down side is that I guess that returning multiple services, and then looking for the event in each of them is not as fast as a single dictionary lookup from a single service. And using the MS.Ext package adds overhead and perf things.
  2. Add a new method to IAppHostBuilder.  This was done so that we could collect all the registrations, and then create and register a single service - reducing memory usage as well as unnecessary overhead of managing multiple services. I think @rmarinho did this for the handlers and was good enough. However as I started adding features, this started getting out of hand(lers ;) 

These issues are only there for the simple fact that we don't want to add overhead and avoid the perf issues with MS.Ext. And reduce work needed to do something simple.

So I was playing around with things and noticed these feature chunks were implemented in a "builder pattern" - most likely because I implemented them to be builders 😉 But, anyway, if we look at the handlers, for each hander registration, we would call ConfigureHandlers on IAppHostBuilder. This adds the mapping to a list and then at Build() time, we create a service collection. With the lifecycle event system, I configure the service and then for each event I would add the event delegate to a list. At Build() time I then collect all the events from the list and pass it to a ILifecycleEventService and register that with the main collection.

My solution for this was to use a new overload of ConfigureServices that instead of having a delegate that passed the IServiceCollection directly to you, I passed a IServiceCollectionBuilder

IAppHostBuilder ConfigureServices<TBuilder>(Action<HostBuilderContext, TBuilder> configureDelegate)
    where TBuilder : IServiceCollectionBuilder, new();

This means that there would only be one builder per concrete type. Each time you use that type, you get that same builder and additions to that builder are stored there. This also allows for some more flexible things without having to modify the main IAppHostBuilder on each feature.

An example of a library doing this for the user would be Essentials: https://github.com/dotnet/maui/blob/dev/native-lifecycle-events/src/Controls/samples/Controls.Sample/Extensions/EssentialsExtensions.cs

It would be consumed by the user in the simplest of ways:

builder
    .ConfigureEssentials((ctx, essentials) =>
    {
        essentials
            .UseMapServiceToken("YOUR-KEY-HERE")
            .AddAppAction("test_action", "Test App Action")
            .AddAppAction("second_action", "Second App Action")
            .OnAppAction(appAction =>
            {
                Debug.WriteLine($"You seem to have arrived from a special place: {appAction.Title} ({appAction.Id})");
            });
    });

When doing a feature, like lifecycle events, this also follows a simple pattern:

.ConfigureLifecycleEvents((ctx, events) =>
{
    events.AddAndroid(lifecycleBuilder => lifecycleBuilder
        .OnStart((a) => LogEvent(nameof(AndroidLifecycle.OnStart)))
        .OnStop((a) => LogEvent(nameof(AndroidLifecycle.OnStop))));
})

Each time either of the methods are called, they use the same builder object. Then, at the end of the day, we have a single builder, which means a single services, which means a single lookup.

PR Checklist

  • Targets the correct branch
  • Tests are passing (or failures are unrelated)
  • Targets a single property for a single control (or intertwined few properties)
  • Adds the property to the appropriate interface
  • Avoids any changes not essential to the handler property
  • Adds the mapping to the PropertyMapper in the handler
  • Adds the mapping method to the Android, iOS, and Standard aspects of the handler
  • Implements the actual property updates (usually in extension methods in the Platform section of Core)
  • Tags ported renderer methods with [PortHandler]
  • Adds an example of the property to the sample project (MainPage)
  • Adds the property to the stub class
  • Implements basic property tests in DeviceTests

Does this PR touch anything that might effect accessibility?

  • APIs that modify focusability?
  • APIs that modify any text property on a control?
  • Does this PR modify view nesting or view arragement in anyway?
  • Is there the smallest possibility that your PR will change accessibility?
  • I'm not sure, please help me

If any of the above checkboxes apply to your PR then the PR will need to provide testing to demonstrate that accessibility still works.

@mattleibow mattleibow mentioned this pull request Mar 28, 2021
1 task
@mattleibow mattleibow requested review from PureWeen, hartez, jsuarezruiz and rmarinho and removed request for rmarinho March 29, 2021 04:56
- fonts into the new builder system
- handlers now are configure/add
- compat renderers are added like normal handlers
- add a fe UseXxx extensions for "features"
@mattleibow
Copy link
Member Author

mattleibow commented Mar 29, 2021

So far I have this

Startup Plugins
image image

@mattleibow mattleibow changed the base branch from main to dev/consistencify-services April 2, 2021 04:28
@mattleibow mattleibow marked this pull request as ready for review April 4, 2021 20:35
Comment on lines +30 to +37
#if !WINDOWS_UWP
if (string.IsNullOrEmpty(args))
{
var cliArgs = Environment.GetCommandLineArgs();
if (cliArgs?.Length > 1)
args = cliArgs[1];
}
#endif
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is for this bug: microsoft/microsoft-ui-xaml#2005

Base automatically changed from dev/consistencify-services to main April 5, 2021 17:49
@PureWeen PureWeen requested a review from Redth April 5, 2021 18:44
@Redth Redth merged commit 5039e5d into main Apr 5, 2021
@Redth Redth deleted the dev/native-lifecycle-events branch April 5, 2021 21:31
@samhouts samhouts added area-essentials Essentials: Device, Display, Connectivity, Secure Storage, Sensors, App Info platform/android platform/ios platform/windows labels Jul 11, 2023
@github-actions github-actions bot locked and limited conversation to collaborators Dec 22, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-essentials Essentials: Device, Display, Connectivity, Secure Storage, Sensors, App Info fixed-in-6.0.100-preview.3.3 platform/android platform/ios platform/windows

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants