Skip to content
This repository has been archived by the owner on May 1, 2024. It is now read-only.

[WIP] Fluent API + Micosoft.Extensions.Hosting for initialization of Xamarin.Forms #8220

Closed
wants to merge 42 commits into from
Closed

[WIP] Fluent API + Micosoft.Extensions.Hosting for initialization of Xamarin.Forms #8220

wants to merge 42 commits into from

Conversation

KSemenenko
Copy link
Contributor

@KSemenenko KSemenenko commented Oct 25, 2019

Description of Change

What if we were to add a "Fluent API" for initialization of Xamarin.Forms? Instead of calling several Init methods that are not very intuitive or discoverable, we can use a fluent mechanism.
Based on @jamesmontemagno post
https://montemagno.com/add-asp-net-cores-dependency-injection-into-xamarin-apps-with-hostbuilder/

@nickrandolph thanks for the idea with the article.

Issues Resolved

var app = Forms.Create(this, bundle)
	            .WithFlags("UseLegacyRenderers")
	            .WithMaps()
	            .WithVisualMaterial()
	            .WithAppLinks()
	            .UseStartup<Startup>()
	            .NativeConfigureHostConfiguration(c =>
	            {
	                // do some native configuration
	            })
	            .NativeConfigureServices((h, s) =>
	            {
	            	// do some native configuration
                        s.AddSingleton<INativeDataService, MyNativeDataService>();
	            })
                    .NativeConfigureAppConfiguration((h, s) =>
	            {
	            	// do some native configuration
	            })
	            .Build<App>();

LoadApplication(app);

API Changes

new property in Application

IServiceProvider Application.ServiceProvider { get; set; }

new interfaces:

public interface IFormsBuilder
{
    IFormsBuilder PostInit(Action action);
    IFormsBuilder PreInit(Action action);
    
    void Init();

    Application Build(Type app);
    TApp Build<TApp>() where TApp : Application;
    TApp Build<TApp>(Func<TApp> createApp) where TApp : Application;

    IFormsBuilder UseStartup(Type startupType);
    IFormsBuilder UseStartup<TStartup>() where TStartup : IStartup, new();
    IFormsBuilder UseStartup<TStartup>(Func<TStartup> createStartup) where TStartup : IStartup;

    IFormsBuilder NativeConfigureServices(Action<HostBuilderContext, IServiceCollection> configureDelegate);
    IFormsBuilder NativeConfigureHostConfiguration(Action<IConfigurationBuilder> configureDelegate);
    IFormsBuilder NativeConfigureAppConfiguration(Action<HostBuilderContext, IConfigurationBuilder> configureDelegate);
}
public interface IStartup
{
    void ConfigureServices(HostBuilderContext hostBuilderContext, IServiceCollection services);
    void ConfigureHostConfiguration(IConfigurationBuilder configurationBuilder);
    void ConfigureAppConfiguration(HostBuilderContext hostBuilderContext, IConfigurationBuilder configurationBuilder);
}

Xamarin Forms Init part:

now we can write extensions for pre init:

public static class FormsBuilderExtensions
{
	public static IFormsBuilder WithFlags(this IFormsBuilder init, params string[] flags)
	{
		return init.PreInit(() => Forms.SetFlags(flags));
	}
}

and we can write extensions for post init:

public static class FormsBuilderExtensions
{
	public static IFormsBuilder WithAppLinks(this IFormsBuilder init, Activity activity)
	{
		return init.PostInit(() => AndroidAppLinks.Init(activity));
	}
}

DI + HostBuilder part:

We can create Startup.cs, and use it .UseStartup<Startup>()

public class Startup : IStartup
{
    public void ConfigureServices(HostBuilderContext hostBuilderContext, IServiceCollection services)
    {
	if (hostBuilderContext.HostingEnvironment.IsDevelopment())
        {
            var world = hostBuilderContext.Configuration["Hello"];
        }

	services.AddSingleton<IDataService, MyDataService>();
	services.AddTransient<MyViewModel>();
	services.AddTransient<MainPage>();
	services.AddSingleton<App>();
    }

    public void ConfigureHostConfiguration(IConfigurationBuilder configurationBuilder)
    {

    }

    public void ConfigureAppConfiguration(HostBuilderContext hostBuilderContext, IConfigurationBuilder configurationBuilder)
    {
	
    }
}

minimal call

var app = Forms.Create()
	            .Build<App>();

LoadApplication(app);

or just Init for backward compatibility

Forms.Create().Init();
var app = new App();

We can use the service provider to get instances of the class.

public App()
{
    InitializeComponent();
    MainPage = Application.ServiceProvider.GetService<MainPage>();
}

EmbeddedResourceLoader for load embedded resources.
An important condition is set the calling assembly by calling SetExecutingAssembly
this is done in the Build method in the FormsBuild class

public static class EmbeddedResourceLoader
{
    public static void SetExecutingAssembly(Assembly assembly);
    public static byte[] GetEmbeddedResourceBytes(string resourceFileName);
    public static byte[] GetEmbeddedResourceBytes(string resourceFileName, Assembly assembly);
    public static string GetEmbeddedResourcePath(string resourceFileName);
    public static string GetEmbeddedResourcePath(string resourceFileName, Assembly assembly);
    public static Stream GetEmbeddedResourceStream(string resourceFileName);
    public static Stream GetEmbeddedResourceStream(string resourceFileName, Assembly assembly);
    public static string GetEmbeddedResourceString(string resourceFileName);
    public static string GetEmbeddedResourceString(string resourceFileName, Assembly assembly);
    public static ImageSource GetImageSource(string name);
    public static ImageSource GetImageSource(string name, Assembly assembly);
}

Platforms Affected

  • Core (all platforms)
  • iOS
  • Android
  • UWP
  • Tizen
  • WPF
  • MacOS
  • GTK

.NET Standards 2.0 required.

Links to the following NuGet are required.

Microsoft.Extensions.Hosting

Settings file should be added to the Forms project

appsettings.json 

Testing Procedure

Make sure that all test projects are launched as before.

PR Checklist

  • Targets the correct branch
  • Tests are passing (or failures are unrelated)

@KSemenenko
Copy link
Contributor Author

KSemenenko commented Oct 25, 2019

@samhouts @jsuarezruiz @jfversluis Can I ask you to run the tests please?

@StephaneDelcroix
Copy link
Member

I don't like t have 2 APIs to achieve the same goal. If you prefer having everything fluent, feel free to pack that in a separate nuget, as extensions methods, use it and share it

@KSemenenko
Copy link
Contributor Author

KSemenenko commented Nov 27, 2019

@jfversluis Microsoft.Extensions.Hosting is only for .netstandard 2.0 so this is a problem.

@KSemenenko
Copy link
Contributor Author

KSemenenko commented Nov 27, 2019

@jfversluis Or maybe we can make our own analogue? With the same api, but with support .net standard 1?
https://github.com/aspnet/Extensions/tree/master/src/Hosting

And call it Xamarin.Forms.Hosting

@KSemenenko KSemenenko changed the title Fluent API + Micosoft.Extensions.Hosting for initialization of Xamarin.Forms [WIP] Fluent API + Micosoft.Extensions.Hosting for initialization of Xamarin.Forms Dec 13, 2019
@pstricks-fans
Copy link

pstricks-fans commented Feb 11, 2020

@KSemenenko : Compared with Asp.net Core that deliberately has no IStartup for some reasons, do you have any reason to define IStartup for Xamarin.Forms?

Seriously, I love your idea. I really want your work above to be one of Xamarin.Forms project templates in .net core SDK.

I also made a request How to implement DI in Xamarin.Forms.

@KSemenenko
Copy link
Contributor Author

Hi @pstricks-fans, this was done for quick implementation.
I think Asp.NET uses some kind of reflection, and I would also like IStartup to be optional.
But for now this is just an idea.

@marinasundstrom
Copy link

@KSemenenko Would this allow me access and swap out XF infrastructure? Factories and resolvers etc.

@snow-jallen
Copy link

Yes yes yes please! Xamarin.Forms feels so klunky without proper DI support. Prism helps, but anyone tried doing a hamburger menu with prism? Yikes. Shell is nice, but you give up viewmodel-first navigation and DI...not worth it.

@rmarinho
Copy link
Member

Let's work on this on #12460 12460

@rmarinho rmarinho closed this Oct 26, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Enhancement] EmbeddedResourceLoader [Spec] Use the Fluent API for initialization