reactiveui / ReactiveUI

An advanced, composable, functional reactive model-view-viewmodel framework for all .NET platforms that is inspired by functional reactive programming. ReactiveUI allows you to abstract mutable state away from your user interfaces, express the idea around a feature in one readable place and improve the testability of your application.
https://www.reactiveui.net
MIT License
8.04k stars 1.12k forks source link

Improve the Initialization of RxUI #1737

Open glennawatson opened 6 years ago

glennawatson commented 6 years ago

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.

stale[bot] commented 5 years ago

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.

glennawatson commented 4 years ago

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.

MovGP0 commented 4 years ago

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();
}
RLittlesII commented 4 years ago

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.

InquisitorJax commented 2 years ago

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)

glennawatson commented 2 years ago

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.

InquisitorJax commented 2 years ago

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).

pellet commented 1 month ago

This fixed the issue for me in my MAUI project(targeting net8.0-windows10.0.19041.0), I just called this before creating a new page: PlatformRegistrationManager.SetRegistrationNamespaces(RegistrationNamespace.Maui);