Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
wants to merge 42 commits into
base: master
from

Conversation

@KSemenenko
Copy link
Contributor

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

This comment has been minimized.

Copy link
Contributor Author

KSemenenko commented Oct 25, 2019

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

KSemenenko added 5 commits Oct 25, 2019
@StephaneDelcroix

This comment has been minimized.

Copy link
Member

StephaneDelcroix commented Oct 25, 2019

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

This comment has been minimized.

Copy link
Contributor Author

KSemenenko commented Nov 18, 2019

I added System.Threading.Tasks.Extensions @jfversluis can you run build please?

@KSemenenko

This comment has been minimized.

Copy link
Contributor Author

KSemenenko commented Nov 18, 2019

@jfversluis can you run build one more time? sorry for this :( seems I found real error

@KSemenenko

This comment has been minimized.

Copy link
Contributor Author

KSemenenko commented Nov 18, 2019

Maybe I need to do something with nuspec? @jfversluis

@KSemenenko

This comment has been minimized.

Copy link
Contributor Author

KSemenenko commented Nov 18, 2019

Can you run the build again @jfversluis? I already added the link wherever possible.

@KSemenenko

This comment has been minimized.

Copy link
Contributor Author

KSemenenko commented Nov 19, 2019

I added some tests, @jfversluis can you please run the build when you have time.

@jfversluis

This comment has been minimized.

Copy link
Member

jfversluis commented Nov 19, 2019

Triggered another build. I don't think that adding another enhancement to this PR is really something we want though. This one is big enough as it is, no need to make it bigger. This will only make it harder for us to review and thus is will take longer.

Please keep your PRs as small as possible :)

@KSemenenko

This comment has been minimized.

Copy link
Contributor Author

KSemenenko commented Nov 19, 2019

Thanks @jfversluis :) this PR uses EmbeddedResourceLoader to load appsettings.json so this is part of this PR.
and I thought that this should be highlighted somehow, so as not to forget.

@KSemenenko

This comment has been minimized.

Copy link
Contributor Author

KSemenenko commented Nov 19, 2019

this is strange, all errors remained, although I already added links to all projects.

@KSemenenko

This comment has been minimized.

Copy link
Contributor Author

KSemenenko commented Nov 19, 2019

I added links to Nuget which were in error, @jfversluis can I ask you to run the build again?

@KSemenenko

This comment has been minimized.

Copy link
Contributor Author

KSemenenko commented Nov 19, 2019

this is so strange, I added all package links :(

@KSemenenko

This comment has been minimized.

Copy link
Contributor Author

KSemenenko commented Nov 19, 2019

If someone knows what is wrong with NuGet I will be happy for any help

@anaximander23

This comment has been minimized.

Copy link

anaximander23 commented Nov 19, 2019

I'd love something like this - in fact, I wrote my own mini-framework on top of Xamarin.Forms that does exactly this. It's on GitHub, and while it's still in its infancy, there's enough there for you to see the rough pattern. For example, this is how you initialise the app:

var appCore = ApplicationCoreBuilder
    .Create()
    .UseStartup<Startup>()
    .OnAndroid(this)
    .Build();

LoadApplication(appCore);

Seeing a syntax like this become part of Xamarin.Forms itself would be great.

@KSemenenko KSemenenko changed the title Fluent API + DI for initialization of Xamarin.Forms Fluent API + Micosoft.Extensions.Hosting for initialization of Xamarin.Forms Nov 25, 2019
KSemenenko added 6 commits Nov 25, 2019
# Conflicts:
#	Xamarin.Forms.Maps.UWP/Xamarin.Forms.Maps.UWP.csproj
@KSemenenko

This comment has been minimized.

Copy link
Contributor Author

KSemenenko commented Nov 25, 2019

Basically, all the code is ready but I found that UWP is using .net standard 1.0 and for work I need .net standard 2.0
So we need to make a decision about how to deal with the .net standard and NuGet versions of dependencies, I think that I can not add dependencies and change versions at my discretion.
Therefore, I need your opinion and advice.
@jfversluis @davidortinau @jamesmontemagno @jsuarezruiz

@jfversluis

This comment has been minimized.

Copy link
Member

jfversluis commented Nov 27, 2019

I think we're not going to drop netstandard1 anytime soon there #8570

@KSemenenko

This comment has been minimized.

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

This comment has been minimized.

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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.