ReactiveUI icon indicating copy to clipboard operation
ReactiveUI copied to clipboard

Improve the Initialization of RxUI

Open glennawatson opened this issue 7 years ago • 8 comments

The Initialization of RxUI is a bit clunky at the moment

https://github.com/reactiveui/rfcs/issues/14

Implement a change around the discussion in the RFC.

glennawatson avatar Sep 01 '18 01:09 glennawatson

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. If you want this issue progressed faster please start a conversation about raising a pull-request or coordinating your pull-request with a maintainer to get it merged. Understand that if folks like yourself don't contribute, ReactiveUI won't grow. You may or may not know this but ReactiveUI is maintained by unpaid volunteers. The maintainers put up a big marketing front but at it's core is a couple of passionate folks. ReactiveUI cares about open-source sustainability as maintainers have a serious load on their shoulders. Consumers shouldn't be naive in thinking that the latest update to a nuget package just magically materializes from the ethers. These things happen because our peers make them happen. No-one wants a tragedy of the commons situation. I urge you to get involved. Thank-you.

stale[bot] avatar Oct 31 '18 01:10 stale[bot]

Ana came up with a point that at the moment on the platforms it works it works.

On the original RFC there was the following comment

So instead of something like options.UseDefaults() we would add some additional verbosity by splitting options out into their own functions?

Like

RxUIOptions options = new RxUIOptions(); options.UsePlatformSchedulers(); options.UsePlatformBindingConverters(); options.UsePlatformActivationForViewFetcher(); // etc.. Presumably the equivalent to options.UseDefaults() is simply RxApp.Start()? So what's the difference between this:

RxApp.Start(new RxUIOptions()); and this:

RxApp.Start(); or even this:

RxApp.Start(null); Is the first just an "empty" RxApp whereas the second is an RxApp with all of the default platform registrations? (and the last is an ArgumentNullException?) I think if that's the case we might want to look at something like RxApp.StartDefault() instead of RxApp.Start() just to help make the behavior less surprising.

Also, would we be able to call RxApp.Start() multiple times with different options? What sort of bugs would we run into if that was supported? I would like to be able to call Start() multiple times so I can use different options for different unit tests, but calling Start() twice in a normal application seems like something that we probably don't want to support.

Just thinking we'd probably want to take advantage of the Target Framework where we can with a generic "Start()", maybe we can make this a extension method against our NuGet packages.

Also our error messages have to be really consistent if we are in a non-initialized state if possible since at the moment the users really have to hunt the documentation.

glennawatson avatar Oct 05 '19 18:10 glennawatson

why not use something like:

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Splat.Microsoft.Extensions.DependencyInjection;
// ...

public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
       using(var host = BuildHost(e.Args))
       {
           host.Start();
           var services = host.Services;
           services.UseMicrosoftDependencyResolver();

            using (var mainScope = services.CreateScope())
            {
               var mainWindow = (Window) mainScope.ServiceProvider
                   .GetRequiredService<IViewFor<MainWindowViewModel>();

                mainWindow.Show();
            }
        }
    }

    private IHost BuildHost(string[] args)
    {
        Host.CreateDefaultBuilder(args)
            .ConfigureAppConfiguration((builderContext, config) => {
                IHostEnvironment env = builderContext.HostEnvironment;
                config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
                    .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);

                // ...
            })
            .ConfigureServices(services => {
                // configure Splat to use Microsoft DI
                services.UseMicrosoftDependencyResolver();
                var resolver = Splat.Locator.CurrentMutable;
                resolver.InitializeSplat();
                resolver.InitializeReactiveUI();

                // add ReactiveUI 
                services.AddRxUI();

                services.AddTransient<MainWindowViewModel>();
                services.AddTransient<IViewFor<MainWindowViewModel>, MainWindow>();

                // TODO: configure other services (user controls, viewmodels, services)

                services.Configure<RxUIOptions>(options => {
                    options.UsePlatformSchedulers();
                    options.UsePlatformBindingConverters();
                    options.UsePlatformActivationForViewFetcher();
                });

                // TODO: configure other options
            })
            .ConfigureLogging(logger => {
                // TODO: configure logging
            })
            .Build();
    }
}

Using Splat in ViewModel:

public sealed MainViewModel : ReactiveObject, IActivatableViewModel 
{
        public MainViewModel()
        {
             var scopeFactory = Splat.Locator.Current.GetService<IServiceProvider>();

             this.WhenActivated(d => {
                 var scope = scopeFactory.CreateScope().DisposeWith(d);
                 var services = scope.ServiceProvider;

                 // get other services from services
            );
        }

        public ViewModelActivator Activator { get; } = new ViewModelActivator();
}

MovGP0 avatar Oct 11 '19 08:10 MovGP0

I think the over all plan is to give the flexibility of some of the .NET Core Startup concepts, but keep a simple RxApp.InitializeWPF() option. This would institute a huge breaking change and for consumers that go from not having to do anything to initialize ReactiveUI to having to provide an entire Startup file might feel overwhelming. So providing a default init method per platform that gives you OOTB what you get today is ideal.

RLittlesII avatar Oct 11 '19 12:10 RLittlesII

I know this is an old thread, but do we have an eta on delivering this? I think it preferable to have an init() method implemented rather than having to click continue 3 or 4 times for FileNotFoundException every time I start an app with CLR exception management turned on. As an aside - I tried just unchecking the System.IO.FileNotFoundException (or adding a condition for ReactiveUI.dll) - but this doesn't seem to work :( (VS for Windows)

InquisitorJax avatar May 16 '22 09:05 InquisitorJax

Use https://github.com/reactiveui/ReactiveUI/blob/fa25c488d4997f75d2c034e17b240ecbb549a65a/src/ReactiveUI/PlatformRegistrationManager.cs#L25 before any other call to RxUI and register the platforms you are using.

glennawatson avatar May 16 '22 09:05 glennawatson

Thanks @glennawatson We're using Forms, and I've registered it as suggested: PlatformRegistrationManager.SetRegistrationNamespaces(RegistrationNamespace.XamForms);

The result, however, is that it still moans about trying to find XamForms (but at least it doesn't about the other platforms :) )

image

Also, this seems to happen when running the iOS project, but I'm not seeing it when running Android (likely because the startup code does touch anything reactiveUI related).

InquisitorJax avatar May 16 '22 12:05 InquisitorJax