Skip to content

Commit

Permalink
Cleanup ReactNativeAppBuilder and ReactNativeWin32App (microsoft#13983)
Browse files Browse the repository at this point in the history
This PR simplifies and scopes down the API for `ReactNativeAppBuilder` and `ReactNativeWin32App`.

- Bug fix (non-breaking change which fixes an issue)

`ReactNativeAppBuilder`'s API surface made it too easy to call incorrectly and not realize it.

Resolves microsoft#13946

There are a variety of changes to the API surface:
* `ReactInstanceSettingsBuilder` deleted: not only are there are simply too many APIs that would need to be exposed to be useful, the very act of creating and replacing the app's `ReactNativeHost`'s `ReactInstanceSettings` with a new one is what caused the bug in microsoft#13946 in the first place
* `ReactNativeAppBuilder` now only exposes APIs to specify the intial, non-ReactNative, WinAppSDK types, (i.e. `DispatcherQueueController`, `Compositor`, and `AppWindow`), objects the app developer may already have created for their existing app, and otherwise is only responsible for building a `ReactNativeWin32App` with those types properly pre-made
* `ReactNativeWin32App::Start()` is now more responsible for the stitching together all of the relevant types to make a working Win32 fabric app
* All WinRT APIs without an immediate use-case have been commented out until we are sure they are necessary and that it is safe to expose them
* The template has been updated to follow the pattern of:
    * Use `ReactNativeAppBuilder` to get a `ReactNativeWin32` app with the base WinAppSDK types ready
    * Get and modify the types as necessary from the created app object (like the `ReactInstanceSettings` and the `AppWindow`)
    * Call `app.Start()`

N/A

Verified new apps and example apps in libraryes build and run properly.

Should this change be included in the release notes: _yes_

Cleanup ReactNativeAppBuilder and ReactNativeWin32App
  • Loading branch information
jonthysell authored and rnbot committed Nov 2, 2024
1 parent 4294d05 commit f1b00d2
Show file tree
Hide file tree
Showing 10 changed files with 239 additions and 421 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "Cleanup ReactNativeAppBuilder and ReactNativeWin32App",
"packageName": "react-native-windows",
"email": "[email protected]",
"dependentChangeType": "patch"
}
59 changes: 0 additions & 59 deletions vnext/Microsoft.ReactNative/ReactInstanceSettingsBuilder.cpp

This file was deleted.

23 changes: 0 additions & 23 deletions vnext/Microsoft.ReactNative/ReactInstanceSettingsBuilder.h

This file was deleted.

154 changes: 25 additions & 129 deletions vnext/Microsoft.ReactNative/ReactNativeAppBuilder.cpp
Original file line number Diff line number Diff line change
@@ -1,43 +1,17 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

#include "pch.h"
#include "ReactNativeAppBuilder.h"
#include "ReactNativeAppBuilder.g.cpp"
#include "IReactDispatcher.h"
#include "ReactNativeHost.h"

#include "ReactNativeWin32App.h"

#include "winrt/Microsoft.ReactNative.h"
#include "winrt/Microsoft.UI.Composition.h"
#include "winrt/Microsoft.UI.Dispatching.h"
#include "winrt/Microsoft.UI.Interop.h"
#include "winrt/Microsoft.UI.Windowing.h"
#include "winrt/microsoft.UI.Interop.h"

// Scaling factor for the window's content based on the DPI of the display where the window is located.
float ScaleFactor(HWND hwnd) noexcept {
return GetDpiForWindow(hwnd) / static_cast<float>(USER_DEFAULT_SCREEN_DPI);
}

void UpdateRootViewSizeToAppWindow(
winrt::Microsoft::ReactNative::ReactNativeIsland const &rootView,
winrt::Microsoft::UI::Windowing::AppWindow const &window) {
auto hwnd = winrt::Microsoft::UI::GetWindowFromWindowId(window.Id());
auto scaleFactor = ScaleFactor(hwnd);
winrt::Windows::Foundation::Size size{
window.ClientSize().Width / scaleFactor, window.ClientSize().Height / scaleFactor};
// Do not relayout when minimized
if (window.Presenter().as<winrt::Microsoft::UI::Windowing::OverlappedPresenter>().State() !=
winrt::Microsoft::UI::Windowing::OverlappedPresenterState::Minimized) {
winrt::Microsoft::ReactNative::LayoutConstraints constraints;
constraints.LayoutDirection = winrt::Microsoft::ReactNative::LayoutDirection::Undefined;
constraints.MaximumSize = constraints.MinimumSize = size;
rootView.Arrange(constraints, {0, 0});
}
}

namespace winrt::ReactNative {
using namespace winrt::Microsoft::ReactNative;
}

namespace winrt::UI {
using namespace winrt::Microsoft::UI;
}

namespace winrt::Microsoft::ReactNative::implementation {
ReactNativeAppBuilder::ReactNativeAppBuilder() {
Expand All @@ -46,133 +20,55 @@ ReactNativeAppBuilder::ReactNativeAppBuilder() {

ReactNativeAppBuilder::~ReactNativeAppBuilder() {}

winrt::ReactNative::ReactNativeAppBuilder ReactNativeAppBuilder::AddPackageProviders(
winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::ReactNative::IReactPackageProvider> const
&packageProviders) {
for (auto const &provider : packageProviders) {
m_reactNativeWin32App.ReactNativeHost().PackageProviders().Append(provider);
}

return *this;
}

winrt::ReactNative::ReactNativeAppBuilder ReactNativeAppBuilder::SetReactInstanceSettings(
winrt::Microsoft::ReactNative::ReactInstanceSettings const &settings) {
m_reactNativeWin32App.ReactNativeHost().InstanceSettings(settings);

winrt::Microsoft::ReactNative::ReactNativeAppBuilder ReactNativeAppBuilder::SetDispatcherQueueController(
winrt::Microsoft::UI::Dispatching::DispatcherQueueController const &dispatcherQueueController) {
m_reactNativeWin32App.as<implementation::ReactNativeWin32App>().get()->DispatcherQueueController(
dispatcherQueueController);
return *this;
}

winrt::ReactNative::ReactNativeAppBuilder ReactNativeAppBuilder::SetCompositor(
winrt::Microsoft::ReactNative::ReactNativeAppBuilder ReactNativeAppBuilder::SetCompositor(
winrt::Microsoft::UI::Composition::Compositor const &compositor) {
m_reactNativeWin32App.as<implementation::ReactNativeWin32App>().get()->Compositor(compositor);
return *this;
}

winrt::ReactNative::ReactNativeAppBuilder ReactNativeAppBuilder::SetAppWindow(
winrt::Microsoft::ReactNative::ReactNativeAppBuilder ReactNativeAppBuilder::SetAppWindow(
winrt::Microsoft::UI::Windowing::AppWindow const &appWindow) {
m_reactNativeWin32App.as<implementation::ReactNativeWin32App>().get()->AppWindow(appWindow);

return *this;
}

winrt::Microsoft::ReactNative::ReactNativeAppBuilder ReactNativeAppBuilder::SetReactViewOptions(
winrt::Microsoft::ReactNative::ReactViewOptions const &reactViewOptions) {
m_reactViewOptions = reactViewOptions;
winrt::Microsoft::ReactNative::ReactNativeWin32App ReactNativeAppBuilder::Build() {
// Create the DispatcherQueueController if the app developer doesn't provide one
if (m_reactNativeWin32App.as<implementation::ReactNativeWin32App>().get()->DispatcherQueueController() == nullptr) {
assert(m_reactNativeWin32App.as<implementation::ReactNativeWin32App>().get()->Compositor() == nullptr);

return *this;
}

winrt::ReactNative::ReactNativeWin32App ReactNativeAppBuilder::Build() {
if (m_reactNativeWin32App.Compositor() == nullptr) {
// Create a DispatcherQueue for this thread. This is needed for Composition, Content, and
// Input APIs.
// Create a DispatcherQueue for this thread. This is needed for Composition, Content, and Input APIs.
auto dispatcherQueueController =
winrt::Microsoft::UI::Dispatching::DispatcherQueueController::CreateOnCurrentThread();

m_reactNativeWin32App.as<implementation::ReactNativeWin32App>().get()->DispatchQueueController(
m_reactNativeWin32App.as<implementation::ReactNativeWin32App>().get()->DispatcherQueueController(
dispatcherQueueController);
}

// Create the compositor on behalf of the App Developer
// Create the Compositor if the app developer doesn't provide one
if (m_reactNativeWin32App.as<implementation::ReactNativeWin32App>().get()->Compositor() == nullptr) {
// Create the compositor on behalf of the App Developer.
auto compositor = winrt::Microsoft::UI::Composition::Compositor();
m_reactNativeWin32App.as<implementation::ReactNativeWin32App>().get()->Compositor(compositor);
}

// Create the AppWindow if the developer doesn't provide one
// Create the AppWindow if the app developer doesn't provide one
if (m_reactNativeWin32App.AppWindow() == nullptr) {
auto appWindow = winrt::Microsoft::UI::Windowing::AppWindow::Create();
appWindow.Title(L"SampleApplication");
appWindow.Title(L"ReactNativeWin32App");
appWindow.Resize({1000, 1000});
appWindow.Show();

m_reactNativeWin32App.as<implementation::ReactNativeWin32App>().get()->AppWindow(appWindow);
}

// Currently set the property to use current thread dispatcher as a default UI dispatcher.
// TODO: Provision for setting dispatcher based on the thread dispatcherQueueController is created.
m_reactNativeWin32App.ReactNativeHost().InstanceSettings().Properties().Set(
ReactDispatcherHelper::UIDispatcherProperty(), ReactDispatcherHelper::UIThreadDispatcher());

auto hwnd{winrt::UI::GetWindowFromWindowId(m_reactNativeWin32App.AppWindow().Id())};

winrt::ReactNative::ReactCoreInjection::SetTopLevelWindowId(
m_reactNativeWin32App.ReactNativeHost().InstanceSettings().Properties(), reinterpret_cast<uint64_t>(hwnd));

winrt::ReactNative::Composition::CompositionUIService::SetCompositor(
m_reactNativeWin32App.ReactNativeHost().InstanceSettings(), m_reactNativeWin32App.Compositor());

// Start the react-native instance, which will create a JavaScript runtime and load the applications bundle.
m_reactNativeWin32App.ReactNativeHost().ReloadInstance();

// Create a RootView which will present a react-native component
auto reactNativeIsland = winrt::Microsoft::ReactNative::ReactNativeIsland(m_reactNativeWin32App.Compositor());
reactNativeIsland.ReactViewHost(winrt::Microsoft::ReactNative::ReactCoreInjection::MakeViewHost(
m_reactNativeWin32App.ReactNativeHost(), m_reactViewOptions));

m_reactNativeWin32App.as<implementation::ReactNativeWin32App>().get()->ReactNativeIsland(
std::move(reactNativeIsland));

// Update the size of the RootView when the AppWindow changes size
m_reactNativeWin32App.AppWindow().Changed(
[wkRootView = winrt::make_weak(m_reactNativeWin32App.ReactNativeIsland())](
winrt::Microsoft::UI::Windowing::AppWindow const &window,
winrt::Microsoft::UI::Windowing::AppWindowChangedEventArgs const &args) {
if (args.DidSizeChange() || args.DidVisibilityChange()) {
if (auto rootView = wkRootView.get()) {
UpdateRootViewSizeToAppWindow(rootView, window);
}
}
});

// Quit application when main window is closed
m_reactNativeWin32App.AppWindow().Destroying([this](
winrt::Microsoft::UI::Windowing::AppWindow const &window,
winrt::Windows::Foundation::IInspectable const & /*args*/) {
// Before we shutdown the application - unload the ReactNativeHost to give the javascript a chance to save any
// state
auto async = m_reactNativeWin32App.ReactNativeHost().UnloadInstance();
async.Completed([this](auto asyncInfo, winrt::Windows::Foundation::AsyncStatus asyncStatus) {
assert(asyncStatus == winrt::Windows::Foundation::AsyncStatus::Completed);
m_reactNativeWin32App.ReactNativeHost().InstanceSettings().UIDispatcher().Post([]() { PostQuitMessage(0); });
});
});

// DesktopChildSiteBridge create a ContentSite that can host the RootView ContentIsland
auto desktopChildSiteBridge = winrt::Microsoft::UI::Content::DesktopChildSiteBridge::Create(
m_reactNativeWin32App.Compositor(), m_reactNativeWin32App.AppWindow().Id());

desktopChildSiteBridge.Connect(m_reactNativeWin32App.ReactNativeIsland().Island());

desktopChildSiteBridge.ResizePolicy(winrt::Microsoft::UI::Content::ContentSizePolicy::ResizeContentToParentWindow);

auto scaleFactor = ScaleFactor(hwnd);
m_reactNativeWin32App.ReactNativeIsland().ScaleFactor(scaleFactor);

UpdateRootViewSizeToAppWindow(reactNativeIsland, m_reactNativeWin32App.AppWindow());

m_reactNativeWin32App.as<implementation::ReactNativeWin32App>().get()->DesktopChildSiteBridge(
std::move(desktopChildSiteBridge));

return m_reactNativeWin32App;
}

Expand Down
18 changes: 5 additions & 13 deletions vnext/Microsoft.ReactNative/ReactNativeAppBuilder.h
Original file line number Diff line number Diff line change
@@ -1,32 +1,24 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#pragma once

#include "ReactNativeAppBuilder.g.h"
#include <winrt/Microsoft.UI.Content.h>

namespace winrt::Microsoft::ReactNative::implementation {
struct ReactNativeAppBuilder : ReactNativeAppBuilderT<ReactNativeAppBuilder> {
ReactNativeAppBuilder();

~ReactNativeAppBuilder();

winrt::Microsoft::ReactNative::ReactNativeAppBuilder AddPackageProviders(
winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::ReactNative::IReactPackageProvider> const
&packageProviders);
winrt::Microsoft::ReactNative::ReactNativeAppBuilder SetReactInstanceSettings(
winrt::Microsoft::ReactNative::ReactInstanceSettings const &settings);

// TODO: Currently, SetCompositor API is not exposed to the developer.
// Compositor depends on the DispatcherQueue created by DispatcherQueueController on a current thread
// or dedicated thread. So we also have to make a provision for setting DispatcherQueueController.
winrt::Microsoft::ReactNative::ReactNativeAppBuilder SetDispatcherQueueController(
winrt::Microsoft::UI::Dispatching::DispatcherQueueController const &dispatcherQueueController);
winrt::Microsoft::ReactNative::ReactNativeAppBuilder SetCompositor(
winrt::Microsoft::UI::Composition::Compositor const &compositor);
winrt::Microsoft::ReactNative::ReactNativeAppBuilder SetAppWindow(
winrt::Microsoft::UI::Windowing::AppWindow const &appWindow);
winrt::Microsoft::ReactNative::ReactNativeAppBuilder SetReactViewOptions(
winrt::Microsoft::ReactNative::ReactViewOptions const &reactViewOptions);
winrt::Microsoft::ReactNative::ReactNativeWin32App Build();

private:
winrt::Microsoft::ReactNative::ReactViewOptions m_reactViewOptions{};
winrt::Microsoft::ReactNative::ReactNativeWin32App m_reactNativeWin32App{nullptr};
};
} // namespace winrt::Microsoft::ReactNative::implementation
Expand Down
Loading

0 comments on commit f1b00d2

Please sign in to comment.