k1mmm / rnwmultiwindow

react-native-windows multi-window example
3 stars 0 forks source link

Using ApplicationView instead of AppWindow #1

Open juliesaia-vendora opened 3 months ago

juliesaia-vendora commented 3 months ago

Firstly thank you so much for this repo, its a huge help :)

I'm trying to support Windows 10 1809, which is right before they added AppWindow to WinRT. I was wondering if you had any idea how to rework some of this to use ApplicationView instead? I tried this:

        public async void OpenSecondaryWindow()
        {
            CoreApplicationView newView = CoreApplication.CreateNewView();
            int newViewId = 0;
            await newView.Dispatcher.RunAsync(
                CoreDispatcherPriority.Normal,
                () =>
                {
                    Frame frame = new Frame();
                    frame.Navigate(typeof(SecondaryWindow), null);
                    Window.Current.Content = frame;
                    // You have to activate the window in order to show it later.
                    Window.Current.Activate();

                    newViewId = ApplicationView.GetForCurrentView().Id;
                }
            );
            bool viewShown = await ApplicationViewSwitcher.TryShowAsStandaloneAsync(newViewId);
        }

Which does open the window, but it crashes when actually initializing the react instance (i.e. when it logs "running app with props ...}

I've been reading that ApplicationView opens a separate thread and so the two windows can't share a React instance, but I've seen others say that the two windows would have the same JS engine, so I'm not sure if this is possible or not.

k1mmm commented 3 months ago

@juliesaia-vendora No problem! =)

Unfortunately, if you want to share the same React instance, I haven't found any other way to make it work. But even though they are running in different React Instances you could still implement something like ICP (Inter Communication Protocol) where you send data from one react instance to the c++/c# layer and then back to the other react instance, but this approach is a real pain to support, especially if you wanna synchronice some kind of data state.

juliesaia-vendora commented 3 months ago

Ok, that's what I thought 😅

Do you know how to create 2 separate react native hosts? All I know is the one thats already on App Host. My use case only really needs one way communication from the main window to the secondary, so I don't think this is a hugely problematic constraint

k1mmm commented 3 months ago

If I remember it correctly you create a new "React Instance" from the host, and assign it to the the new window, have a look at this documentation and see if it can help you =)

https://microsoft.github.io/react-native-windows/docs/ReactNativeHost

juliesaia-vendora commented 3 months ago

Sorry to bother you about this again, I think I'm really close but still getting a crash once the bundle completes, right after "Running component with...."

The only error message I can see is hresult_wrong_thread in VS, which shows on a breakpoint in the "ApplicationView ASTA" thread, maybe that should be the main UI thread somehow?

I tried to follow this guide for making a new ReactNativeHost https://github.com/microsoft/react-native-windows/discussions/10817#discussioncomment-5546474

I also tried separating the two instances into two index.js'es, but no dice.

Here's my attempt, I was making a new ReactNativeHost in the constructor of App, then calling ReactNativeHost.ReloadInstance() after creating the window in OnLaunched

App.xaml.cs:

using System;
using System.Diagnostics;
using System.Threading.Tasks;
using Microsoft.ReactNative;
using Windows.ApplicationModel.Activation;
using Windows.ApplicationModel.Core;
using Windows.UI.Core;
using Windows.UI.ViewManagement;
using Windows.UI.WindowManagement;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Hosting;

namespace vendora
{
    sealed partial class App : ReactApplication
    {
        public IReactContext ReactContext { get; set; }

        public ReactNativeHost SecondaryHost;

        public App()
        {
#if BUNDLE
            JavaScriptBundleFile = "index.windows";
            InstanceSettings.UseFastRefresh = false;
#else
            JavaScriptBundleFile = "index";
            InstanceSettings.UseFastRefresh = true;
#endif

#if DEBUG
            InstanceSettings.UseDirectDebugger = true;
            InstanceSettings.UseDeveloperSupport = true;
#else
            InstanceSettings.UseDirectDebugger = false;
            InstanceSettings.UseDeveloperSupport = false;
#endif

            Microsoft.ReactNative.Managed.AutolinkedNativeModules.RegisterAutolinkedNativeModulePackages(
                PackageProviders
            ); // Includes any autolinked modules

            PackageProviders.Add(new ReactPackageProvider());

            Host.InstanceSettings.InstanceCreated += OnReactInstanceCreated;

            // create another ReactNativeHost

            ReactInstanceSettings ris = new ReactInstanceSettings()
            {
#if BUNDLE
                JavaScriptBundleFile = "index_LegacyWindowsCFD.windows",
                UseFastRefresh = false,
#else
                JavaScriptBundleFile = "index_LegacyWindowsCFD",
                UseFastRefresh = true,
#endif

#if DEBUG
                UseDirectDebugger = true,
                UseDeveloperSupport = true,
#else
                UseDirectDebugger = false,
                UseDeveloperSupport = false,
#endif
            };

            ris.Properties.Set(
                ReactPropertyBagHelper.GetName(
                    ReactPropertyBagHelper.GetNamespace("ReactNative.Dispatcher"),
                    "UIDispatcher"
                ),
                ris.UIDispatcher
            );

            SecondaryHost = new ReactNativeHost() { InstanceSettings = ris, };

            Microsoft.ReactNative.Managed.AutolinkedNativeModules.RegisterAutolinkedNativeModulePackages(
                SecondaryHost.PackageProviders
            );
            SecondaryHost.PackageProviders.Add(new ReactPackageProvider());

            InitializeComponent();
        }

        private void OnReactInstanceCreated(object sender, InstanceCreatedEventArgs args)
        {
            // This event is triggered when a React Native instance is created.
            // At this point, the ReactContext becomes valid.
            // However, it's better to emit events only after the JS code that can handle them is loaded.

            // If you need to perform any initialization based on ReactContext creation, do it here.
            // capsterNativeEmitter._reactContext = args.Context;
            this.ReactContext = args.Context;
        }

        /// <summary>
        /// Invoked when the application is launched normally by the end user.  Other entry points
        /// will be used such as when the application is launched to open a specific file.
        /// </summary>
        /// <param name="e">Details about the launch request and process.</param>
        protected override void OnLaunched(LaunchActivatedEventArgs e)
        {
            base.OnLaunched(e);
            var frame = (Frame)Window.Current.Content;
            frame.Navigate(typeof(MainPage), e.Arguments);
            OpenSecondaryWindow();
        }

        /// <summary>
        /// Invoked when the application is activated by some means other than normal launching.
        /// </summary>
        protected override void OnActivated(
            Windows.ApplicationModel.Activation.IActivatedEventArgs e
        )
        {
            var preActivationContent = Window.Current.Content;
            base.OnActivated(e);
            if (preActivationContent == null && Window.Current != null)
            {
                // Display the initial content
                var frame = (Frame)Window.Current.Content;
                frame.Navigate(typeof(MainPage), null);
            }
        }

        public async Task OpenSecondaryWindow()
        {
            CoreApplicationView newView = CoreApplication.CreateNewView();
            int newViewId = 0;
            await newView.Dispatcher.RunAsync(
                CoreDispatcherPriority.Normal,
                () =>
                {
                    Frame frame = new Frame();
                    frame.Navigate(typeof(SecondaryWindow), null);
                    Window.Current.Content = frame;
                    // You have to activate the window in order to show it later.
                    Window.Current.Activate();

                    newViewId = ApplicationView.GetForCurrentView().Id;
                }
            );
            bool viewShown = await ApplicationViewSwitcher.TryShowAsStandaloneAsync(newViewId);

            // load react instance
            SecondaryHost.ReloadInstance();
        }
    }
}

SecondaryWindow.xaml.cs:

        public SecondaryWindow()
        {
            this.InitializeComponent();

            var app = Application.Current as App;
            secondaryWindowReactRootView.ReactNativeHost = app.SecondaryHost;
        }
juliesaia-vendora commented 3 months ago

Got past the thread issue by including ReloadInstance in the dispatcher, but I'm just about ready to give up on this and try to update to 19H1...

Getting a crash in AddView in PaperUIManagerModule.cpp with no exception message at all, definitely out of my depth here

image

image