Skip to content

Commit

Permalink
feat(RCTAppDelegate): Implement RCTRootViewFactory (#42263)
Browse files Browse the repository at this point in the history
Summary:
This PR implements `RCTRootViewFactory` a utility class (suggested by cipolleschi) that returns proper RCTRootView based on the current environment state (new arch/old arch/bridgeless). This class aims to preserve background compatibility by implementing a configuration class forwarding necessary class to RCTAppDelegate.

This PR leverages the `RCTRootViewFactory` in `RCTAppDelegate` for the default initialization of React Native (greenfield).

Here is an example of creating a Brownfield integration (without RCTAppDelegate) using this class (can be later added to docs):

1. Store reference to `rootViewFactory` and to `UIWindow`

`AppDelegate.h`:
```objc
interface AppDelegate : UIResponder <UIApplicationDelegate>

property(nonatomic, strong) UIWindow* window;
property(nonatomic, strong) RCTRootViewFactory* rootViewFactory;

end
```

2. Create an initial configuration using `RCTRootViewFactoryConfiguration` and initialize `RCTRootViewFactory` using it. Then you can use the factory to create a new `RCTRootView` without worrying about old arch/new arch/bridgeless.

 `AppDelegate.mm`
```objc
implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary<UIApplicationLaunchOptionsKey,id> *)launchOptions {

  // Create configuration
 RCTRootViewFactoryConfiguration *configuration = [[RCTRootViewFactoryConfiguration alloc] initWithBundleURL:self.bundleURL
                                                                                                 newArchEnabled:self.fabricEnabled
                                                                                             turboModuleEnabled:self.turboModuleEnabled
                                                                                              bridgelessEnabled:self.bridgelessEnabled];

  // Initialize RCTRootViewFactory
  self.rootViewFactory = [[RCTRootViewFactory alloc] initWithConfiguration:configuration];

  // Create main root view
  UIView *rootView = [self.rootViewFactory viewWithModuleName:@"RNTesterApp" initialProperties:@{} launchOptions:launchOptions];

  // Set main window as you prefer for your Brownfield integration.
  self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
  UIViewController *rootViewController = [UIViewController new];
  rootViewController.view = rootView;
  self.window.rootViewController = rootViewController;
  [self.window makeKeyAndVisible];

  // Later in the codebase you can initialize more rootView's using rootViewFactory.

  return YES;
}
end
```
bypass-github-export-checks

[INTERNAL] [ADDED] - Implement RCTRootViewFactory

Pull Request resolved: #42263

Test Plan: Check if root view is properly created on app initialization

Reviewed By: dmytrorykun

Differential Revision: D53179625

Pulled By: cipolleschi

fbshipit-source-id: 9bc850965ba30d84ad3e67d91dd888f0547c2136
  • Loading branch information
okwasniewski authored and huntie committed Mar 11, 2024
1 parent 6440e35 commit 66b1cfe
Show file tree
Hide file tree
Showing 6 changed files with 441 additions and 144 deletions.
13 changes: 5 additions & 8 deletions packages/react-native/Libraries/AppDelegate/RCTAppDelegate.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#import <React/RCTBridgeDelegate.h>
#import <UIKit/UIKit.h>
#import "RCTRootViewFactory.h"

@class RCTBridge;
@protocol RCTBridgeDelegate;
Expand Down Expand Up @@ -57,9 +58,12 @@ NS_ASSUME_NONNULL_BEGIN

/// The window object, used to render the UViewControllers
@property (nonatomic, strong, nonnull) UIWindow *window;
@property (nonatomic, strong, nullable) RCTBridge *bridge;
@property (nonatomic, nullable) RCTBridge *bridge;
@property (nonatomic, strong, nullable) NSString *moduleName;
@property (nonatomic, strong, nullable) NSDictionary *initialProps;
@property (nonatomic, strong, nonnull) RCTRootViewFactory *rootViewFactory;

@property (nonatomic, nullable) RCTSurfacePresenterBridgeAdapter *bridgeAdapter;

/**
* It creates a `RCTBridge` using a delegate and some launch options.
Expand Down Expand Up @@ -126,13 +130,6 @@ NS_ASSUME_NONNULL_BEGIN
*/
- (void)setRootView:(UIView *)rootView toRootViewController:(UIViewController *)rootViewController;

/// This method controls whether the App will use RuntimeScheduler. Only applicable in the legacy architecture.
///
/// @return: `YES` to use RuntimeScheduler, `NO` to use JavaScript scheduler. The default value is `YES`.
- (BOOL)runtimeSchedulerEnabled;

@property (nonatomic, strong) RCTSurfacePresenterBridgeAdapter *bridgeAdapter;

/// This method returns a map of Component Descriptors and Components classes that needs to be registered in the
/// new renderer. The Component Descriptor is a string which represent the name used in JS to refer to the native
/// component. The default implementation returns an empty dictionary. Subclasses can override this method to register
Expand Down
186 changes: 56 additions & 130 deletions packages/react-native/Libraries/AppDelegate/RCTAppDelegate.mm
Original file line number Diff line number Diff line change
Expand Up @@ -39,85 +39,28 @@
#import <react/renderer/runtimescheduler/RuntimeSchedulerCallInvoker.h>
#import <react/runtime/JSRuntimeFactory.h>

@interface RCTAppDelegate () <
RCTTurboModuleManagerDelegate,
RCTComponentViewFactoryComponentProvider,
RCTContextContainerHandling> {
std::shared_ptr<const facebook::react::ReactNativeConfig> _reactNativeConfig;
facebook::react::ContextContainer::Shared _contextContainer;
}
@interface RCTAppDelegate () <RCTComponentViewFactoryComponentProvider, RCTTurboModuleManagerDelegate>
@end

static NSDictionary *updateInitialProps(NSDictionary *initialProps, BOOL isFabricEnabled)
{
NSMutableDictionary *mutableProps = [initialProps mutableCopy] ?: [NSMutableDictionary new];
return mutableProps;
}

@interface RCTAppDelegate () <RCTCxxBridgeDelegate> {
std::shared_ptr<facebook::react::RuntimeScheduler> _runtimeScheduler;
}
@end

@implementation RCTAppDelegate {
RCTHost *_reactHost;
}

- (instancetype)init
{
if (self = [super init]) {
_contextContainer = std::make_shared<facebook::react::ContextContainer const>();
_reactNativeConfig = std::make_shared<facebook::react::EmptyReactNativeConfig const>();
_contextContainer->insert("ReactNativeConfig", _reactNativeConfig);
}
return self;
}
@implementation RCTAppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
RCTSetNewArchEnabled([self newArchEnabled]);
BOOL enableTM = self.turboModuleEnabled;
BOOL fabricEnabled = self.fabricEnabled;
BOOL enableBridgeless = self.bridgelessEnabled;

NSDictionary *initProps = updateInitialProps([self prepareInitialProps], fabricEnabled);
RCTAppSetupPrepareApp(application, self.turboModuleEnabled);

RCTAppSetupPrepareApp(application, enableTM);
self.rootViewFactory = [self createRCTRootViewFactory];

UIView *rootView;
if (enableBridgeless) {
// Enable native view config interop only if both bridgeless mode and Fabric is enabled.
RCTSetUseNativeViewConfigsInBridgelessMode(fabricEnabled);
UIView *rootView = [self.rootViewFactory viewWithModuleName:self.moduleName
initialProperties:self.initialProps
launchOptions:launchOptions];

// Enable TurboModule interop by default in Bridgeless mode
RCTEnableTurboModuleInterop(YES);
RCTEnableTurboModuleInteropBridgeProxy(YES);

[self createReactHost];
if (self.newArchEnabled || self.fabricEnabled) {
[RCTComponentViewFactory currentComponentViewFactory].thirdPartyFabricComponentsProvider = self;
RCTFabricSurface *surface = [_reactHost createSurfaceWithModuleName:self.moduleName initialProperties:initProps];

RCTSurfaceHostingProxyRootView *surfaceHostingProxyRootView = [[RCTSurfaceHostingProxyRootView alloc]
initWithSurface:surface
sizeMeasureMode:RCTSurfaceSizeMeasureModeWidthExact | RCTSurfaceSizeMeasureModeHeightExact];

rootView = (RCTRootView *)surfaceHostingProxyRootView;
rootView.backgroundColor = [UIColor systemBackgroundColor];
} else {
if (!self.bridge) {
self.bridge = [self createBridgeWithDelegate:self launchOptions:launchOptions];
}
if ([self newArchEnabled]) {
self.bridgeAdapter = [[RCTSurfacePresenterBridgeAdapter alloc] initWithBridge:self.bridge
contextContainer:_contextContainer];
self.bridge.surfacePresenter = self.bridgeAdapter.surfacePresenter;

[RCTComponentViewFactory currentComponentViewFactory].thirdPartyFabricComponentsProvider = self;
}
rootView = [self createRootViewWithBridge:self.bridge moduleName:self.moduleName initProps:initProps];
}
[self _logWarnIfCreateRootViewWithBridgeIsOverridden];
[self customizeRootView:(RCTRootView *)rootView];

self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
UIViewController *rootViewController = [self createRootViewController];
[self setRootView:rootView toRootViewController:rootViewController];
Expand All @@ -140,21 +83,11 @@ - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
return nil;
}

- (NSDictionary *)prepareInitialProps
{
return self.initialProps;
}

- (RCTBridge *)createBridgeWithDelegate:(id<RCTBridgeDelegate>)delegate launchOptions:(NSDictionary *)launchOptions
{
return [[RCTBridge alloc] initWithDelegate:delegate launchOptions:launchOptions];
}

- (void)customizeRootView:(RCTRootView *)rootView
{
// Override point for customization after application launch.
}

- (UIView *)createRootViewWithBridge:(RCTBridge *)bridge
moduleName:(NSString *)moduleName
initProps:(NSDictionary *)initProps
Expand Down Expand Up @@ -192,9 +125,9 @@ - (void)setRootView:(UIView *)rootView toRootViewController:(UIViewController *)
rootViewController.view = rootView;
}

- (BOOL)runtimeSchedulerEnabled
- (void)customizeRootView:(RCTRootView *)rootView
{
return YES;
// Override point for customization after application launch.
}

#pragma mark - UISceneDelegate
Expand All @@ -207,25 +140,6 @@ - (void)windowScene:(UIWindowScene *)windowScene
[[NSNotificationCenter defaultCenter] postNotificationName:RCTWindowFrameDidChangeNotification object:self];
}

#pragma mark - RCTCxxBridgeDelegate

- (std::unique_ptr<facebook::react::JSExecutorFactory>)jsExecutorFactoryForBridge:(RCTBridge *)bridge
{
_runtimeScheduler = std::make_shared<facebook::react::RuntimeScheduler>(RCTRuntimeExecutorFromBridge(bridge));
if ([self newArchEnabled]) {
std::shared_ptr<facebook::react::CallInvoker> callInvoker =
std::make_shared<facebook::react::RuntimeSchedulerCallInvoker>(_runtimeScheduler);
RCTTurboModuleManager *turboModuleManager = [[RCTTurboModuleManager alloc] initWithBridge:bridge
delegate:self
jsInvoker:callInvoker];
_contextContainer->erase("RuntimeScheduler");
_contextContainer->insert("RuntimeScheduler", _runtimeScheduler);
return RCTAppSetupDefaultJsExecutorFactory(bridge, turboModuleManager, _runtimeScheduler);
} else {
return RCTAppSetupJsExecutorFactoryForOldArch(bridge, _runtimeScheduler);
}
}

#pragma mark - New Arch Enabled settings

- (BOOL)newArchEnabled
Expand All @@ -252,11 +166,33 @@ - (BOOL)bridgelessEnabled
return [self newArchEnabled];
}

#pragma mark - RCTComponentViewFactoryComponentProvider
- (NSURL *)bundleURL
{
[NSException raise:@"RCTAppDelegate::bundleURL not implemented"
format:@"Subclasses must implement a valid getBundleURL method"];
return nullptr;
}

- (NSDictionary<NSString *, Class<RCTComponentViewProtocol>> *)thirdPartyFabricComponents
#pragma mark - Bridge and Bridge Adapter properties

- (RCTBridge *)bridge
{
return @{};
return self.rootViewFactory.bridge;
}

- (RCTSurfacePresenterBridgeAdapter *)bridgeAdapter
{
return self.rootViewFactory.bridgeAdapter;
}

- (void)setBridge:(RCTBridge *)bridge
{
self.rootViewFactory.bridge = bridge;
}

- (void)setBridgeAdapter:(RCTSurfacePresenterBridgeAdapter *)bridgeAdapter
{
self.rootViewFactory.bridgeAdapter = bridgeAdapter;
}

#pragma mark - RCTTurboModuleManagerDelegate
Expand Down Expand Up @@ -288,43 +224,33 @@ - (Class)getModuleClassFromName:(const char *)name
return RCTAppSetupDefaultModuleFromClass(moduleClass);
}

#pragma mark - New Arch Utilities
#pragma mark - RCTComponentViewFactoryComponentProvider

- (void)createReactHost
- (NSDictionary<NSString *, Class<RCTComponentViewProtocol>> *)thirdPartyFabricComponents
{
__weak __typeof(self) weakSelf = self;
_reactHost = [[RCTHost alloc] initWithBundleURL:[self bundleURL]
hostDelegate:nil
turboModuleManagerDelegate:self
jsEngineProvider:^std::shared_ptr<facebook::react::JSRuntimeFactory>() {
return [weakSelf createJSRuntimeFactory];
}];
[_reactHost setBundleURLProvider:^NSURL *() {
return [weakSelf bundleURL];
}];
[_reactHost setContextContainerHandler:self];
[_reactHost start];
return @{};
}

- (std::shared_ptr<facebook::react::JSRuntimeFactory>)createJSRuntimeFactory
- (RCTRootViewFactory *)createRCTRootViewFactory
{
#if USE_HERMES
return std::make_shared<facebook::react::RCTHermesInstance>(_reactNativeConfig, nullptr);
#else
return std::make_shared<facebook::react::RCTJscInstance>();
#endif
}
RCTRootViewFactoryConfiguration *configuration =
[[RCTRootViewFactoryConfiguration alloc] initWithBundleURL:self.bundleURL
newArchEnabled:self.fabricEnabled
turboModuleEnabled:self.turboModuleEnabled
bridgelessEnabled:self.bridgelessEnabled];

- (void)didCreateContextContainer:(std::shared_ptr<facebook::react::ContextContainer>)contextContainer
{
contextContainer->insert("ReactNativeConfig", _reactNativeConfig);
}
__weak __typeof(self) weakSelf = self;
configuration.createRootViewWithBridge = ^UIView *(RCTBridge *bridge, NSString *moduleName, NSDictionary *initProps)
{
return [weakSelf createRootViewWithBridge:bridge moduleName:moduleName initProps:initProps];
};

- (NSURL *)bundleURL
{
[NSException raise:@"RCTAppDelegate::bundleURL not implemented"
format:@"Subclasses must implement a valid getBundleURL method"];
return nullptr;
configuration.createBridgeWithDelegate = ^RCTBridge *(id<RCTBridgeDelegate> delegate, NSDictionary *launchOptions)
{
return [weakSelf createBridgeWithDelegate:delegate launchOptions:launchOptions];
};

return [[RCTRootViewFactory alloc] initWithConfiguration:configuration andTurboModuleManagerDelegate:self];
}

@end
Loading

0 comments on commit 66b1cfe

Please sign in to comment.