-
Notifications
You must be signed in to change notification settings - Fork 24.9k
iOS: Add new RCTCustomBundleConfiguration
for modifying bundle URL
#54006
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
coado
wants to merge
1
commit into
facebook:main
Choose a base branch
from
coado:bundle-url-ios
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+312
−70
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
coado
added a commit
to coado/react-native
that referenced
this pull request
Oct 15, 2025
…acebook#54006) Summary: Following the [RFC](react-native-community/discussions-and-proposals#933), this PR introduces a new `RCTCustomBundleConfiguration` interface for modifying the bundle URL and exposes a new API for setting its instance in the `RCTReactNativeFactory`. The configuration object includes: - bundleFilePath - the URL of the bundle to load from the file system, - packagerServerScheme - the server scheme (e.g. http or https) to use when loading from the packager, - packagerServerHost - the server host (e.g. localhost) to use when loading from the packager. It associates `RCTPackagerConnection` (previously singleton) with the `RCTDevSettings` instance, which has access to the `RCTBundleManager`, which contains the specified configuration object. The connection is now established in the `RCTDevSettings initialize` method, called after the bundle manager is set by invoking the new `startWithBundleManager` method on the `RCTPackagerConnection`. The `RCTCustomBundleConfiguration` allows only for either `bundleFilePath` or `(packagerServerScheme, packagerServerHost)` to be set by defining appropriate initializers. The logic for creating bundle URL query items is extracted to a separate `createJSBundleURLQuery` method and is used by `RCTBundleManager` to set the configured `packagerServerHost` and `packagerServerScheme`. If the configuration is not defined, the `getBundleURL` method returns the result of the passed `fallbackURLProvider`. The `bundleFilePath` should be created as `[NSURL fileURLWithPath:<path>]`, as otherwise the HMR client is created and fails ungracefully. The check is added in the `getBundle` method to log the error beforehand. When the `bundleFilePath` is set in the `RCTCustomBundleConfiguration` the `Connect to Metro...` message shouldn't be suggested. ## Changelog: [IOS][ADDED] - Add new `RCTCustomBundleConfiguration` for modifying bundle URL on `RCTReactNativeFactory`. Test Plan: Tested changing `packagerServerHost` from the `AppDelegate` by re-creating the React Native instance with updated `RCTCustomBundleConfiguration`. I've run two Metro instances, each serving a different JS bundle (changed background) on `8081` and `8082` ports. The native `Restart RN:<current port>` button on top of the screen toggles between ports (used in bundle configuration) and re-creates connections. Tested with `RCT_DEV` set to true and false. https://github.com/user-attachments/assets/fd57068b-869c-4f45-93be-09d33f691cea <details> <summary>code:</summary> `AppDelegate.mm` ```objc #import "AppDelegate.h" #import <UserNotifications/UserNotifications.h> #import <React/RCTBundleManager.h> #import <React/RCTBundleURLProvider.h> #import <React/RCTDefines.h> #import <React/RCTLinkingManager.h> #import <ReactCommon/RCTSampleTurboModule.h> #import <ReactCommon/RCTTurboModuleManager.h> #import <React/RCTPushNotificationManager.h> #import <NativeCxxModuleExample/NativeCxxModuleExample.h> #ifndef RN_DISABLE_OSS_PLUGIN_HEADER #import <RNTMyNativeViewComponentView.h> #endif #if __has_include(<ReactAppDependencyProvider/RCTAppDependencyProvider.h>) #define USE_OSS_CODEGEN 1 #import <ReactAppDependencyProvider/RCTAppDependencyProvider.h> #else #define USE_OSS_CODEGEN 0 #endif static NSString *kBundlePath = @"js/RNTesterApp.ios"; interface AppDelegate () <UNUserNotificationCenterDelegate> end implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.launchOptions = launchOptions; self.port = @"8081"; #if USE_OSS_CODEGEN self.dependencyProvider = [RCTAppDependencyProvider new]; #endif self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; [self startReactNative]; [[UNUserNotificationCenter currentNotificationCenter] setDelegate:self]; return YES; } - (void)startReactNative { self.reactNativeFactory = [[RCTReactNativeFactory alloc] initWithDelegate:self]; NSString *packagerServerHost = [NSString stringWithFormat:@"localhost:%@", self.port]; RCTCustomBundleConfiguration *customBundleConfiguration = [[RCTCustomBundleConfiguration alloc] initWithPackagerServerScheme:@"http" packagerServerHost:packagerServerHost]; self.reactNativeFactory.customBundleConfiguration = customBundleConfiguration; [self.reactNativeFactory startReactNativeWithModuleName:@"RNTesterApp" inWindow:self.window initialProperties:[self prepareInitialProps] launchOptions:self.launchOptions]; [self createTopButton]; } - (void)createTopButton { NSString *title = [NSString stringWithFormat:@"Restart RN:%@", self.port]; self.topButton = [UIButton buttonWithType:UIButtonTypeSystem]; [self.topButton setTitle:title forState:UIControlStateNormal]; [self.topButton setBackgroundColor:[UIColor colorWithRed:0.0 green:0.5 blue:1.0 alpha:1]]; [self.topButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; CGFloat buttonWidth = 120; CGFloat buttonHeight = 44; CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width; self.topButton.frame = CGRectMake((screenWidth - buttonWidth) / 2, 50, buttonWidth, buttonHeight); self.topButton.layer.cornerRadius = 8; [self.topButton addTarget:self action:selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside]; [self.window addSubview:self.topButton]; [self.window bringSubviewToFront:self.topButton]; } - (void)togglePort { self.port = [self.port isEqual: @"8081"] ? @"8082" : @"8081"; } - (void)buttonTapped:(UIButton *)sender { self.reactNativeFactory = nil; [self togglePort]; [self startReactNative]; } - (NSDictionary *)prepareInitialProps { NSMutableDictionary *initProps = [NSMutableDictionary new]; NSString *_routeUri = [[NSUserDefaults standardUserDefaults] stringForKey:@"route"]; if (_routeUri) { initProps[@"exampleFromAppetizeParams"] = [NSString stringWithFormat:@"rntester://example/%Example", _routeUri]; } return initProps; } - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge { return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:kBundlePath]; } - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options { return [RCTLinkingManager application:app openURL:url options:options]; } - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const std::string &)name jsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker { if (name == facebook::react::NativeCxxModuleExample::kModuleName) { return std::make_shared<facebook::react::NativeCxxModuleExample>(jsInvoker); } return [super getTurboModule:name jsInvoker:jsInvoker]; } // Required for the remoteNotificationsRegistered event. - (void)application:(__unused UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { [RCTPushNotificationManager didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; } // Required for the remoteNotificationRegistrationError event. - (void)application:(__unused UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { [RCTPushNotificationManager didFailToRegisterForRemoteNotificationsWithError:error]; } #pragma mark - UNUserNotificationCenterDelegate // Required for the remoteNotificationReceived and localNotificationReceived events - (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler { [RCTPushNotificationManager didReceiveNotification:notification]; completionHandler(UNNotificationPresentationOptionNone); } // Required for the remoteNotificationReceived and localNotificationReceived events // Called when a notification is tapped from background. (Foreground notification will not be shown per // the presentation option selected above). - (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler { UNNotification *notification = response.notification; // This condition will be true if tapping the notification launched the app. if ([response.actionIdentifier isEqualToString:UNNotificationDefaultActionIdentifier]) { // This can be retrieved with getInitialNotification. [RCTPushNotificationManager setInitialNotification:notification]; } [RCTPushNotificationManager didReceiveNotification:notification]; completionHandler(); } #pragma mark - New Arch Enabled settings - (BOOL)bridgelessEnabled { return YES; } #pragma mark - RCTComponentViewFactoryComponentProvider #ifndef RN_DISABLE_OSS_PLUGIN_HEADER - (nonnull NSDictionary<NSString *, Class<RCTComponentViewProtocol>> *)thirdPartyFabricComponents { NSMutableDictionary *dict = [super thirdPartyFabricComponents].mutableCopy; if (!dict[@"RNTMyNativeView"]) { dict[@"RNTMyNativeView"] = NSClassFromString(@"RNTMyNativeViewComponentView"); } if (!dict[@"SampleNativeComponent"]) { dict[@"SampleNativeComponent"] = NSClassFromString(@"RCTSampleNativeComponentComponentView"); } return dict; } #endif - (NSURL *)bundleURL { return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:kBundlePath]; } end ``` `AppDelegate.h` ```objc #import <RCTDefaultReactNativeFactoryDelegate.h> #import <RCTReactNativeFactory.h> #import <UIKit/UIKit.h> interface AppDelegate : RCTDefaultReactNativeFactoryDelegate <UIApplicationDelegate> property (nonatomic, strong, nonnull) UIWindow *window; property (nonatomic, strong, nonnull) RCTReactNativeFactory *reactNativeFactory; property (nonatomic, strong, nullable) UIButton *topButton; property (nonatomic, strong) NSDictionary *launchOptions; property (nonatomic, assign) NSString *port; end ``` </details> Differential Revision: D84058022 Pulled By: coado
coado
added a commit
to coado/react-native
that referenced
this pull request
Oct 17, 2025
…acebook#54006) Summary: Following the [RFC](react-native-community/discussions-and-proposals#933), this PR introduces a new `RCTCustomBundleConfiguration` interface for modifying the bundle URL and exposes a new API for setting its instance in the `RCTReactNativeFactory`. The configuration object includes: - bundleFilePath - the URL of the bundle to load from the file system, - packagerServerScheme - the server scheme (e.g. http or https) to use when loading from the packager, - packagerServerHost - the server host (e.g. localhost) to use when loading from the packager. It associates `RCTPackagerConnection` (previously singleton) with the `RCTDevSettings` instance, which has access to the `RCTBundleManager`, which contains the specified configuration object. The connection is now established in the `RCTDevSettings initialize` method, called after the bundle manager is set by invoking the new `startWithBundleManager` method on the `RCTPackagerConnection`. The `RCTCustomBundleConfiguration` allows only for either `bundleFilePath` or `(packagerServerScheme, packagerServerHost)` to be set by defining appropriate initializers. The logic for creating bundle URL query items is extracted to a separate `createJSBundleURLQuery` method and is used by `RCTBundleManager` to set the configured `packagerServerHost` and `packagerServerScheme`. If the configuration is not defined, the `getBundleURL` method returns the result of the passed `fallbackURLProvider`. The `bundleFilePath` should be created as `[NSURL fileURLWithPath:<path>]`, as otherwise the HMR client is created and fails ungracefully. The check is added in the `getBundle` method to log the error beforehand. When the `bundleFilePath` is set in the `RCTCustomBundleConfiguration` the `Connect to Metro...` message shouldn't be suggested. ## Changelog: [IOS][ADDED] - Add new `RCTCustomBundleConfiguration` for modifying bundle URL on `RCTReactNativeFactory`. Test Plan: Tested changing `packagerServerHost` from the `AppDelegate` by re-creating the React Native instance with updated `RCTCustomBundleConfiguration`. I've run two Metro instances, each serving a different JS bundle (changed background) on `8081` and `8082` ports. The native `Restart RN:<current port>` button on top of the screen toggles between ports (used in bundle configuration) and re-creates connections. Tested with `RCT_DEV` set to true and false. https://github.com/user-attachments/assets/fd57068b-869c-4f45-93be-09d33f691cea <details> <summary>code:</summary> `AppDelegate.mm` ```objc #import "AppDelegate.h" #import <UserNotifications/UserNotifications.h> #import <React/RCTBundleManager.h> #import <React/RCTBundleURLProvider.h> #import <React/RCTDefines.h> #import <React/RCTLinkingManager.h> #import <ReactCommon/RCTSampleTurboModule.h> #import <ReactCommon/RCTTurboModuleManager.h> #import <React/RCTPushNotificationManager.h> #import <NativeCxxModuleExample/NativeCxxModuleExample.h> #ifndef RN_DISABLE_OSS_PLUGIN_HEADER #import <RNTMyNativeViewComponentView.h> #endif #if __has_include(<ReactAppDependencyProvider/RCTAppDependencyProvider.h>) #define USE_OSS_CODEGEN 1 #import <ReactAppDependencyProvider/RCTAppDependencyProvider.h> #else #define USE_OSS_CODEGEN 0 #endif static NSString *kBundlePath = @"js/RNTesterApp.ios"; interface AppDelegate () <UNUserNotificationCenterDelegate> end implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.launchOptions = launchOptions; self.port = @"8081"; #if USE_OSS_CODEGEN self.dependencyProvider = [RCTAppDependencyProvider new]; #endif self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; [self startReactNative]; [[UNUserNotificationCenter currentNotificationCenter] setDelegate:self]; return YES; } - (void)startReactNative { self.reactNativeFactory = [[RCTReactNativeFactory alloc] initWithDelegate:self]; NSString *packagerServerHost = [NSString stringWithFormat:@"localhost:%@", self.port]; RCTCustomBundleConfiguration *customBundleConfiguration = [[RCTCustomBundleConfiguration alloc] initWithPackagerServerScheme:@"http" packagerServerHost:packagerServerHost]; self.reactNativeFactory.customBundleConfiguration = customBundleConfiguration; [self.reactNativeFactory startReactNativeWithModuleName:@"RNTesterApp" inWindow:self.window initialProperties:[self prepareInitialProps] launchOptions:self.launchOptions]; [self createTopButton]; } - (void)createTopButton { NSString *title = [NSString stringWithFormat:@"Restart RN:%@", self.port]; self.topButton = [UIButton buttonWithType:UIButtonTypeSystem]; [self.topButton setTitle:title forState:UIControlStateNormal]; [self.topButton setBackgroundColor:[UIColor colorWithRed:0.0 green:0.5 blue:1.0 alpha:1]]; [self.topButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; CGFloat buttonWidth = 120; CGFloat buttonHeight = 44; CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width; self.topButton.frame = CGRectMake((screenWidth - buttonWidth) / 2, 50, buttonWidth, buttonHeight); self.topButton.layer.cornerRadius = 8; [self.topButton addTarget:self action:selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside]; [self.window addSubview:self.topButton]; [self.window bringSubviewToFront:self.topButton]; } - (void)togglePort { self.port = [self.port isEqual: @"8081"] ? @"8082" : @"8081"; } - (void)buttonTapped:(UIButton *)sender { self.reactNativeFactory = nil; [self togglePort]; [self startReactNative]; } - (NSDictionary *)prepareInitialProps { NSMutableDictionary *initProps = [NSMutableDictionary new]; NSString *_routeUri = [[NSUserDefaults standardUserDefaults] stringForKey:@"route"]; if (_routeUri) { initProps[@"exampleFromAppetizeParams"] = [NSString stringWithFormat:@"rntester://example/%Example", _routeUri]; } return initProps; } - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge { return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:kBundlePath]; } - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options { return [RCTLinkingManager application:app openURL:url options:options]; } - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const std::string &)name jsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker { if (name == facebook::react::NativeCxxModuleExample::kModuleName) { return std::make_shared<facebook::react::NativeCxxModuleExample>(jsInvoker); } return [super getTurboModule:name jsInvoker:jsInvoker]; } // Required for the remoteNotificationsRegistered event. - (void)application:(__unused UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { [RCTPushNotificationManager didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; } // Required for the remoteNotificationRegistrationError event. - (void)application:(__unused UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { [RCTPushNotificationManager didFailToRegisterForRemoteNotificationsWithError:error]; } #pragma mark - UNUserNotificationCenterDelegate // Required for the remoteNotificationReceived and localNotificationReceived events - (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler { [RCTPushNotificationManager didReceiveNotification:notification]; completionHandler(UNNotificationPresentationOptionNone); } // Required for the remoteNotificationReceived and localNotificationReceived events // Called when a notification is tapped from background. (Foreground notification will not be shown per // the presentation option selected above). - (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler { UNNotification *notification = response.notification; // This condition will be true if tapping the notification launched the app. if ([response.actionIdentifier isEqualToString:UNNotificationDefaultActionIdentifier]) { // This can be retrieved with getInitialNotification. [RCTPushNotificationManager setInitialNotification:notification]; } [RCTPushNotificationManager didReceiveNotification:notification]; completionHandler(); } #pragma mark - New Arch Enabled settings - (BOOL)bridgelessEnabled { return YES; } #pragma mark - RCTComponentViewFactoryComponentProvider #ifndef RN_DISABLE_OSS_PLUGIN_HEADER - (nonnull NSDictionary<NSString *, Class<RCTComponentViewProtocol>> *)thirdPartyFabricComponents { NSMutableDictionary *dict = [super thirdPartyFabricComponents].mutableCopy; if (!dict[@"RNTMyNativeView"]) { dict[@"RNTMyNativeView"] = NSClassFromString(@"RNTMyNativeViewComponentView"); } if (!dict[@"SampleNativeComponent"]) { dict[@"SampleNativeComponent"] = NSClassFromString(@"RCTSampleNativeComponentComponentView"); } return dict; } #endif - (NSURL *)bundleURL { return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:kBundlePath]; } end ``` `AppDelegate.h` ```objc #import <RCTDefaultReactNativeFactoryDelegate.h> #import <RCTReactNativeFactory.h> #import <UIKit/UIKit.h> interface AppDelegate : RCTDefaultReactNativeFactoryDelegate <UIApplicationDelegate> property (nonatomic, strong, nonnull) UIWindow *window; property (nonatomic, strong, nonnull) RCTReactNativeFactory *reactNativeFactory; property (nonatomic, strong, nullable) UIButton *topButton; property (nonatomic, strong) NSDictionary *launchOptions; property (nonatomic, assign) NSString *port; end ``` </details> Differential Revision: D84058022 Pulled By: coado
coado
added a commit
to coado/react-native
that referenced
this pull request
Oct 17, 2025
…acebook#54006) Summary: Following the [RFC](react-native-community/discussions-and-proposals#933), this PR introduces a new `RCTCustomBundleConfiguration` interface for modifying the bundle URL and exposes a new API for setting its instance in the `RCTReactNativeFactory`. The configuration object includes: - bundleFilePath - the URL of the bundle to load from the file system, - packagerServerScheme - the server scheme (e.g. http or https) to use when loading from the packager, - packagerServerHost - the server host (e.g. localhost) to use when loading from the packager. It associates `RCTPackagerConnection` (previously singleton) with the `RCTDevSettings` instance, which has access to the `RCTBundleManager`, which contains the specified configuration object. The connection is now established in the `RCTDevSettings initialize` method, called after the bundle manager is set by invoking the new `startWithBundleManager` method on the `RCTPackagerConnection`. The `RCTCustomBundleConfiguration` allows only for either `bundleFilePath` or `(packagerServerScheme, packagerServerHost)` to be set by defining appropriate initializers. The logic for creating bundle URL query items is extracted to a separate `createJSBundleURLQuery` method and is used by `RCTBundleManager` to set the configured `packagerServerHost` and `packagerServerScheme`. If the configuration is not defined, the `getBundleURL` method returns the result of the passed `fallbackURLProvider`. The `bundleFilePath` should be created as `[NSURL fileURLWithPath:<path>]`, as otherwise the HMR client is created and fails ungracefully. The check is added in the `getBundle` method to log the error beforehand. When the `bundleFilePath` is set in the `RCTCustomBundleConfiguration` the `Connect to Metro...` message shouldn't be suggested. ## Changelog: [IOS][ADDED] - Add new `RCTCustomBundleConfiguration` for modifying bundle URL on `RCTReactNativeFactory`. Test Plan: Tested changing `packagerServerHost` from the `AppDelegate` by re-creating the React Native instance with updated `RCTCustomBundleConfiguration`. I've run two Metro instances, each serving a different JS bundle (changed background) on `8081` and `8082` ports. The native `Restart RN:<current port>` button on top of the screen toggles between ports (used in bundle configuration) and re-creates connections. Tested with `RCT_DEV` set to true and false. https://github.com/user-attachments/assets/fd57068b-869c-4f45-93be-09d33f691cea <details> <summary>code:</summary> `AppDelegate.mm` ```objc #import "AppDelegate.h" #import <UserNotifications/UserNotifications.h> #import <React/RCTBundleManager.h> #import <React/RCTBundleURLProvider.h> #import <React/RCTDefines.h> #import <React/RCTLinkingManager.h> #import <ReactCommon/RCTSampleTurboModule.h> #import <ReactCommon/RCTTurboModuleManager.h> #import <React/RCTPushNotificationManager.h> #import <NativeCxxModuleExample/NativeCxxModuleExample.h> #ifndef RN_DISABLE_OSS_PLUGIN_HEADER #import <RNTMyNativeViewComponentView.h> #endif #if __has_include(<ReactAppDependencyProvider/RCTAppDependencyProvider.h>) #define USE_OSS_CODEGEN 1 #import <ReactAppDependencyProvider/RCTAppDependencyProvider.h> #else #define USE_OSS_CODEGEN 0 #endif static NSString *kBundlePath = @"js/RNTesterApp.ios"; interface AppDelegate () <UNUserNotificationCenterDelegate> end implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.launchOptions = launchOptions; self.port = @"8081"; #if USE_OSS_CODEGEN self.dependencyProvider = [RCTAppDependencyProvider new]; #endif self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; [self startReactNative]; [[UNUserNotificationCenter currentNotificationCenter] setDelegate:self]; return YES; } - (void)startReactNative { self.reactNativeFactory = [[RCTReactNativeFactory alloc] initWithDelegate:self]; NSString *packagerServerHost = [NSString stringWithFormat:@"localhost:%@", self.port]; RCTCustomBundleConfiguration *customBundleConfiguration = [[RCTCustomBundleConfiguration alloc] initWithPackagerServerScheme:@"http" packagerServerHost:packagerServerHost]; self.reactNativeFactory.customBundleConfiguration = customBundleConfiguration; [self.reactNativeFactory startReactNativeWithModuleName:@"RNTesterApp" inWindow:self.window initialProperties:[self prepareInitialProps] launchOptions:self.launchOptions]; [self createTopButton]; } - (void)createTopButton { NSString *title = [NSString stringWithFormat:@"Restart RN:%@", self.port]; self.topButton = [UIButton buttonWithType:UIButtonTypeSystem]; [self.topButton setTitle:title forState:UIControlStateNormal]; [self.topButton setBackgroundColor:[UIColor colorWithRed:0.0 green:0.5 blue:1.0 alpha:1]]; [self.topButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; CGFloat buttonWidth = 120; CGFloat buttonHeight = 44; CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width; self.topButton.frame = CGRectMake((screenWidth - buttonWidth) / 2, 50, buttonWidth, buttonHeight); self.topButton.layer.cornerRadius = 8; [self.topButton addTarget:self action:selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside]; [self.window addSubview:self.topButton]; [self.window bringSubviewToFront:self.topButton]; } - (void)togglePort { self.port = [self.port isEqual: @"8081"] ? @"8082" : @"8081"; } - (void)buttonTapped:(UIButton *)sender { self.reactNativeFactory = nil; [self togglePort]; [self startReactNative]; } - (NSDictionary *)prepareInitialProps { NSMutableDictionary *initProps = [NSMutableDictionary new]; NSString *_routeUri = [[NSUserDefaults standardUserDefaults] stringForKey:@"route"]; if (_routeUri) { initProps[@"exampleFromAppetizeParams"] = [NSString stringWithFormat:@"rntester://example/%Example", _routeUri]; } return initProps; } - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge { return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:kBundlePath]; } - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options { return [RCTLinkingManager application:app openURL:url options:options]; } - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const std::string &)name jsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker { if (name == facebook::react::NativeCxxModuleExample::kModuleName) { return std::make_shared<facebook::react::NativeCxxModuleExample>(jsInvoker); } return [super getTurboModule:name jsInvoker:jsInvoker]; } // Required for the remoteNotificationsRegistered event. - (void)application:(__unused UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { [RCTPushNotificationManager didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; } // Required for the remoteNotificationRegistrationError event. - (void)application:(__unused UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { [RCTPushNotificationManager didFailToRegisterForRemoteNotificationsWithError:error]; } #pragma mark - UNUserNotificationCenterDelegate // Required for the remoteNotificationReceived and localNotificationReceived events - (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler { [RCTPushNotificationManager didReceiveNotification:notification]; completionHandler(UNNotificationPresentationOptionNone); } // Required for the remoteNotificationReceived and localNotificationReceived events // Called when a notification is tapped from background. (Foreground notification will not be shown per // the presentation option selected above). - (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler { UNNotification *notification = response.notification; // This condition will be true if tapping the notification launched the app. if ([response.actionIdentifier isEqualToString:UNNotificationDefaultActionIdentifier]) { // This can be retrieved with getInitialNotification. [RCTPushNotificationManager setInitialNotification:notification]; } [RCTPushNotificationManager didReceiveNotification:notification]; completionHandler(); } #pragma mark - New Arch Enabled settings - (BOOL)bridgelessEnabled { return YES; } #pragma mark - RCTComponentViewFactoryComponentProvider #ifndef RN_DISABLE_OSS_PLUGIN_HEADER - (nonnull NSDictionary<NSString *, Class<RCTComponentViewProtocol>> *)thirdPartyFabricComponents { NSMutableDictionary *dict = [super thirdPartyFabricComponents].mutableCopy; if (!dict[@"RNTMyNativeView"]) { dict[@"RNTMyNativeView"] = NSClassFromString(@"RNTMyNativeViewComponentView"); } if (!dict[@"SampleNativeComponent"]) { dict[@"SampleNativeComponent"] = NSClassFromString(@"RCTSampleNativeComponentComponentView"); } return dict; } #endif - (NSURL *)bundleURL { return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:kBundlePath]; } end ``` `AppDelegate.h` ```objc #import <RCTDefaultReactNativeFactoryDelegate.h> #import <RCTReactNativeFactory.h> #import <UIKit/UIKit.h> interface AppDelegate : RCTDefaultReactNativeFactoryDelegate <UIApplicationDelegate> property (nonatomic, strong, nonnull) UIWindow *window; property (nonatomic, strong, nonnull) RCTReactNativeFactory *reactNativeFactory; property (nonatomic, strong, nullable) UIButton *topButton; property (nonatomic, strong) NSDictionary *launchOptions; property (nonatomic, assign) NSString *port; end ``` </details> Differential Revision: D84058022 Pulled By: coado
c904e49
to
8da150a
Compare
coado
added a commit
to coado/react-native
that referenced
this pull request
Oct 20, 2025
…acebook#54006) Summary: Following the [RFC](react-native-community/discussions-and-proposals#933), this PR introduces a new `RCTCustomBundleConfiguration` interface for modifying the bundle URL and exposes a new API for setting its instance in the `RCTReactNativeFactory`. The configuration object includes: - bundleFilePath - the URL of the bundle to load from the file system, - packagerServerScheme - the server scheme (e.g. http or https) to use when loading from the packager, - packagerServerHost - the server host (e.g. localhost) to use when loading from the packager. It associates `RCTPackagerConnection` (previously singleton) with the `RCTDevSettings` instance, which has access to the `RCTBundleManager`, which contains the specified configuration object. The connection is now established in the `RCTDevSettings initialize` method, called after the bundle manager is set by invoking the new `startWithBundleManager` method on the `RCTPackagerConnection`. The `RCTCustomBundleConfiguration` allows only for either `bundleFilePath` or `(packagerServerScheme, packagerServerHost)` to be set by defining appropriate initializers. The logic for creating bundle URL query items is extracted to a separate `createJSBundleURLQuery` method and is used by `RCTBundleManager` to set the configured `packagerServerHost` and `packagerServerScheme`. If the configuration is not defined, the `getBundleURL` method returns the result of the passed `fallbackURLProvider`. The `bundleFilePath` should be created as `[NSURL fileURLWithPath:<path>]`, as otherwise the HMR client is created and fails ungracefully. The check is added in the `getBundle` method to log the error beforehand. When the `bundleFilePath` is set in the `RCTCustomBundleConfiguration` the `Connect to Metro...` message shouldn't be suggested. ## Changelog: [IOS][ADDED] - Add new `RCTCustomBundleConfiguration` for modifying bundle URL on `RCTReactNativeFactory`. Test Plan: Tested changing `packagerServerHost` from the `AppDelegate` by re-creating the React Native instance with updated `RCTCustomBundleConfiguration`. I've run two Metro instances, each serving a different JS bundle (changed background) on `8081` and `8082` ports. The native `Restart RN:<current port>` button on top of the screen toggles between ports (used in bundle configuration) and re-creates connections. Tested with `RCT_DEV` set to true and false. https://github.com/user-attachments/assets/fd57068b-869c-4f45-93be-09d33f691cea <details> <summary>code:</summary> `AppDelegate.mm` ```objc #import "AppDelegate.h" #import <UserNotifications/UserNotifications.h> #import <React/RCTBundleManager.h> #import <React/RCTBundleURLProvider.h> #import <React/RCTDefines.h> #import <React/RCTLinkingManager.h> #import <ReactCommon/RCTSampleTurboModule.h> #import <ReactCommon/RCTTurboModuleManager.h> #import <React/RCTPushNotificationManager.h> #import <NativeCxxModuleExample/NativeCxxModuleExample.h> #ifndef RN_DISABLE_OSS_PLUGIN_HEADER #import <RNTMyNativeViewComponentView.h> #endif #if __has_include(<ReactAppDependencyProvider/RCTAppDependencyProvider.h>) #define USE_OSS_CODEGEN 1 #import <ReactAppDependencyProvider/RCTAppDependencyProvider.h> #else #define USE_OSS_CODEGEN 0 #endif static NSString *kBundlePath = @"js/RNTesterApp.ios"; interface AppDelegate () <UNUserNotificationCenterDelegate> end implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.launchOptions = launchOptions; self.port = @"8081"; #if USE_OSS_CODEGEN self.dependencyProvider = [RCTAppDependencyProvider new]; #endif self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; [self startReactNative]; [[UNUserNotificationCenter currentNotificationCenter] setDelegate:self]; return YES; } - (void)startReactNative { self.reactNativeFactory = [[RCTReactNativeFactory alloc] initWithDelegate:self]; NSString *packagerServerHost = [NSString stringWithFormat:@"localhost:%@", self.port]; RCTCustomBundleConfiguration *customBundleConfiguration = [[RCTCustomBundleConfiguration alloc] initWithPackagerServerScheme:@"http" packagerServerHost:packagerServerHost]; self.reactNativeFactory.customBundleConfiguration = customBundleConfiguration; [self.reactNativeFactory startReactNativeWithModuleName:@"RNTesterApp" inWindow:self.window initialProperties:[self prepareInitialProps] launchOptions:self.launchOptions]; [self createTopButton]; } - (void)createTopButton { NSString *title = [NSString stringWithFormat:@"Restart RN:%@", self.port]; self.topButton = [UIButton buttonWithType:UIButtonTypeSystem]; [self.topButton setTitle:title forState:UIControlStateNormal]; [self.topButton setBackgroundColor:[UIColor colorWithRed:0.0 green:0.5 blue:1.0 alpha:1]]; [self.topButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; CGFloat buttonWidth = 120; CGFloat buttonHeight = 44; CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width; self.topButton.frame = CGRectMake((screenWidth - buttonWidth) / 2, 50, buttonWidth, buttonHeight); self.topButton.layer.cornerRadius = 8; [self.topButton addTarget:self action:selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside]; [self.window addSubview:self.topButton]; [self.window bringSubviewToFront:self.topButton]; } - (void)togglePort { self.port = [self.port isEqual: @"8081"] ? @"8082" : @"8081"; } - (void)buttonTapped:(UIButton *)sender { self.reactNativeFactory = nil; [self togglePort]; [self startReactNative]; } - (NSDictionary *)prepareInitialProps { NSMutableDictionary *initProps = [NSMutableDictionary new]; NSString *_routeUri = [[NSUserDefaults standardUserDefaults] stringForKey:@"route"]; if (_routeUri) { initProps[@"exampleFromAppetizeParams"] = [NSString stringWithFormat:@"rntester://example/%Example", _routeUri]; } return initProps; } - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge { return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:kBundlePath]; } - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options { return [RCTLinkingManager application:app openURL:url options:options]; } - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const std::string &)name jsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker { if (name == facebook::react::NativeCxxModuleExample::kModuleName) { return std::make_shared<facebook::react::NativeCxxModuleExample>(jsInvoker); } return [super getTurboModule:name jsInvoker:jsInvoker]; } // Required for the remoteNotificationsRegistered event. - (void)application:(__unused UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { [RCTPushNotificationManager didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; } // Required for the remoteNotificationRegistrationError event. - (void)application:(__unused UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { [RCTPushNotificationManager didFailToRegisterForRemoteNotificationsWithError:error]; } #pragma mark - UNUserNotificationCenterDelegate // Required for the remoteNotificationReceived and localNotificationReceived events - (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler { [RCTPushNotificationManager didReceiveNotification:notification]; completionHandler(UNNotificationPresentationOptionNone); } // Required for the remoteNotificationReceived and localNotificationReceived events // Called when a notification is tapped from background. (Foreground notification will not be shown per // the presentation option selected above). - (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler { UNNotification *notification = response.notification; // This condition will be true if tapping the notification launched the app. if ([response.actionIdentifier isEqualToString:UNNotificationDefaultActionIdentifier]) { // This can be retrieved with getInitialNotification. [RCTPushNotificationManager setInitialNotification:notification]; } [RCTPushNotificationManager didReceiveNotification:notification]; completionHandler(); } #pragma mark - New Arch Enabled settings - (BOOL)bridgelessEnabled { return YES; } #pragma mark - RCTComponentViewFactoryComponentProvider #ifndef RN_DISABLE_OSS_PLUGIN_HEADER - (nonnull NSDictionary<NSString *, Class<RCTComponentViewProtocol>> *)thirdPartyFabricComponents { NSMutableDictionary *dict = [super thirdPartyFabricComponents].mutableCopy; if (!dict[@"RNTMyNativeView"]) { dict[@"RNTMyNativeView"] = NSClassFromString(@"RNTMyNativeViewComponentView"); } if (!dict[@"SampleNativeComponent"]) { dict[@"SampleNativeComponent"] = NSClassFromString(@"RCTSampleNativeComponentComponentView"); } return dict; } #endif - (NSURL *)bundleURL { return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:kBundlePath]; } end ``` `AppDelegate.h` ```objc #import <RCTDefaultReactNativeFactoryDelegate.h> #import <RCTReactNativeFactory.h> #import <UIKit/UIKit.h> interface AppDelegate : RCTDefaultReactNativeFactoryDelegate <UIApplicationDelegate> property (nonatomic, strong, nonnull) UIWindow *window; property (nonatomic, strong, nonnull) RCTReactNativeFactory *reactNativeFactory; property (nonatomic, strong, nullable) UIButton *topButton; property (nonatomic, strong) NSDictionary *launchOptions; property (nonatomic, assign) NSString *port; end ``` </details> Differential Revision: D84058022 Pulled By: coado
…acebook#54006) Summary: Following the [RFC](react-native-community/discussions-and-proposals#933), this PR introduces a new `RCTCustomBundleConfiguration` interface for modifying the bundle URL and exposes a new API for setting its instance in the `RCTReactNativeFactory`. The configuration object includes: - bundleFilePath - the URL of the bundle to load from the file system, - packagerServerScheme - the server scheme (e.g. http or https) to use when loading from the packager, - packagerServerHost - the server host (e.g. localhost) to use when loading from the packager. It associates `RCTPackagerConnection` (previously singleton) with the `RCTDevSettings` instance, which has access to the `RCTBundleManager`, which contains the specified configuration object. The connection is now established in the `RCTDevSettings initialize` method, called after the bundle manager is set by invoking the new `startWithBundleManager` method on the `RCTPackagerConnection`. The `RCTCustomBundleConfiguration` allows only for either `bundleFilePath` or `(packagerServerScheme, packagerServerHost)` to be set by defining appropriate initializers. The logic for creating bundle URL query items is extracted to a separate `createJSBundleURLQuery` method and is used by `RCTBundleManager` to set the configured `packagerServerHost` and `packagerServerScheme`. If the configuration is not defined, the `getBundleURL` method returns the result of the passed `fallbackURLProvider`. The `bundleFilePath` should be created with `[NSURL fileURLWithPath:<path>]`, as otherwise the HMR client is created and fails ungracefully. The check is added in the `getBundle` method to log the error beforehand: <img width="306" height="822" alt="Simulator Screenshot - iPhone 16 Pro - 2025-10-15 at 17 09 58" src="https://github.com/user-attachments/assets/869eed16-c5d8-4204-81d7-bd9cd42b2223" /> When the `bundleFilePath` is set in the `RCTCustomBundleConfiguration` the `Connect to Metro...` message shouldn't be suggested. ## Changelog: [IOS][ADDED] - Add new `RCTCustomBundleConfiguration` for modifying bundle URL on `RCTReactNativeFactory`. Test Plan: Tested changing `packagerServerHost` from the `AppDelegate` by re-creating the React Native instance with updated `RCTCustomBundleConfiguration`. I've run two Metro instances, each serving a different JS bundle (changed background) on `8081` and `8082` ports. The native `Restart RN:<current port>` button on top of the screen toggles between ports (used in bundle configuration) and re-creates connections. Tested with `RCT_DEV` set to true and false. https://github.com/user-attachments/assets/fd57068b-869c-4f45-93be-09d33f691cea For setting bundle source from a file, I've generated bundle with a blue background and created a custom bundle configuration using `initWithBundleFilePath`. I've run the app without starting Metro: <img width="306" height="822" alt="Simulator Screenshot - iPhone 16 Pro - 2025-10-15 at 17 06 21" src="https://github.com/user-attachments/assets/8283f202-0150-4e93-a4a9-d2b6ea6f1c37" /> <details> <summary>code:</summary> `AppDelegate.mm` ```objc #import "AppDelegate.h" #import <UserNotifications/UserNotifications.h> #import <React/RCTBundleManager.h> #import <React/RCTBundleURLProvider.h> #import <React/RCTDefines.h> #import <React/RCTLinkingManager.h> #import <ReactCommon/RCTSampleTurboModule.h> #import <ReactCommon/RCTTurboModuleManager.h> #import <React/RCTPushNotificationManager.h> #import <NativeCxxModuleExample/NativeCxxModuleExample.h> #ifndef RN_DISABLE_OSS_PLUGIN_HEADER #import <RNTMyNativeViewComponentView.h> #endif #if __has_include(<ReactAppDependencyProvider/RCTAppDependencyProvider.h>) #define USE_OSS_CODEGEN 1 #import <ReactAppDependencyProvider/RCTAppDependencyProvider.h> #else #define USE_OSS_CODEGEN 0 #endif static NSString *kBundlePath = @"js/RNTesterApp.ios"; interface AppDelegate () <UNUserNotificationCenterDelegate> end implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.launchOptions = launchOptions; self.port = @"8081"; #if USE_OSS_CODEGEN self.dependencyProvider = [RCTAppDependencyProvider new]; #endif self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; [self startReactNative]; [[UNUserNotificationCenter currentNotificationCenter] setDelegate:self]; return YES; } - (void)startReactNative { self.reactNativeFactory = [[RCTReactNativeFactory alloc] initWithDelegate:self]; NSString *packagerServerHost = [NSString stringWithFormat:@"localhost:%@", self.port]; RCTCustomBundleConfiguration *customBundleConfiguration = [[RCTCustomBundleConfiguration alloc] initWithPackagerServerScheme:@"http" packagerServerHost:packagerServerHost bundlePath:kBundlePath]; self.reactNativeFactory.customBundleConfiguration = customBundleConfiguration; [self.reactNativeFactory startReactNativeWithModuleName:@"RNTesterApp" inWindow:self.window initialProperties:[self prepareInitialProps] launchOptions:self.launchOptions]; [self createTopButton]; } - (void)createTopButton { NSString *title = [NSString stringWithFormat:@"Restart RN:%@", self.port]; self.topButton = [UIButton buttonWithType:UIButtonTypeSystem]; [self.topButton setTitle:title forState:UIControlStateNormal]; [self.topButton setBackgroundColor:[UIColor colorWithRed:0.0 green:0.5 blue:1.0 alpha:1]]; [self.topButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; CGFloat buttonWidth = 120; CGFloat buttonHeight = 44; CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width; self.topButton.frame = CGRectMake((screenWidth - buttonWidth) / 2, 50, buttonWidth, buttonHeight); self.topButton.layer.cornerRadius = 8; [self.topButton addTarget:self action:selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside]; [self.window addSubview:self.topButton]; [self.window bringSubviewToFront:self.topButton]; } - (void)togglePort { self.port = [self.port isEqual: @"8081"] ? @"8082" : @"8081"; } - (void)buttonTapped:(UIButton *)sender { self.reactNativeFactory = nil; [self togglePort]; [self startReactNative]; } - (NSDictionary *)prepareInitialProps { NSMutableDictionary *initProps = [NSMutableDictionary new]; NSString *_routeUri = [[NSUserDefaults standardUserDefaults] stringForKey:@"route"]; if (_routeUri) { initProps[@"exampleFromAppetizeParams"] = [NSString stringWithFormat:@"rntester://example/%Example", _routeUri]; } return initProps; } - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge { return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:kBundlePath]; } - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options { return [RCTLinkingManager application:app openURL:url options:options]; } - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const std::string &)name jsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker { if (name == facebook::react::NativeCxxModuleExample::kModuleName) { return std::make_shared<facebook::react::NativeCxxModuleExample>(jsInvoker); } return [super getTurboModule:name jsInvoker:jsInvoker]; } // Required for the remoteNotificationsRegistered event. - (void)application:(__unused UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { [RCTPushNotificationManager didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; } // Required for the remoteNotificationRegistrationError event. - (void)application:(__unused UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { [RCTPushNotificationManager didFailToRegisterForRemoteNotificationsWithError:error]; } #pragma mark - UNUserNotificationCenterDelegate // Required for the remoteNotificationReceived and localNotificationReceived events - (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler { [RCTPushNotificationManager didReceiveNotification:notification]; completionHandler(UNNotificationPresentationOptionNone); } // Required for the remoteNotificationReceived and localNotificationReceived events // Called when a notification is tapped from background. (Foreground notification will not be shown per // the presentation option selected above). - (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler { UNNotification *notification = response.notification; // This condition will be true if tapping the notification launched the app. if ([response.actionIdentifier isEqualToString:UNNotificationDefaultActionIdentifier]) { // This can be retrieved with getInitialNotification. [RCTPushNotificationManager setInitialNotification:notification]; } [RCTPushNotificationManager didReceiveNotification:notification]; completionHandler(); } #pragma mark - New Arch Enabled settings - (BOOL)bridgelessEnabled { return YES; } #pragma mark - RCTComponentViewFactoryComponentProvider #ifndef RN_DISABLE_OSS_PLUGIN_HEADER - (nonnull NSDictionary<NSString *, Class<RCTComponentViewProtocol>> *)thirdPartyFabricComponents { NSMutableDictionary *dict = [super thirdPartyFabricComponents].mutableCopy; if (!dict[@"RNTMyNativeView"]) { dict[@"RNTMyNativeView"] = NSClassFromString(@"RNTMyNativeViewComponentView"); } if (!dict[@"SampleNativeComponent"]) { dict[@"SampleNativeComponent"] = NSClassFromString(@"RCTSampleNativeComponentComponentView"); } return dict; } #endif - (NSURL *)bundleURL { return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:kBundlePath]; } end ``` `AppDelegate.h` ```objc #import <RCTDefaultReactNativeFactoryDelegate.h> #import <RCTReactNativeFactory.h> #import <UIKit/UIKit.h> interface AppDelegate : RCTDefaultReactNativeFactoryDelegate <UIApplicationDelegate> property (nonatomic, strong, nonnull) UIWindow *window; property (nonatomic, strong, nonnull) RCTReactNativeFactory *reactNativeFactory; property (nonatomic, strong, nullable) UIButton *topButton; property (nonatomic, strong) NSDictionary *launchOptions; property (nonatomic, assign) NSString *port; end ``` </details> Differential Revision: D84058022 Pulled By: coado
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Labels
CLA Signed
This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.
fb-exported
meta-exported
p: Facebook
Partner: Facebook
p: Software Mansion
Partner: Software Mansion
Partner
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Summary:
Following the RFC, this PR introduces a new
RCTCustomBundleConfiguration
interface for modifying the bundle URL and exposes a new API for setting its instance in theRCTReactNativeFactory
. The configuration object includes:It associates
RCTPackagerConnection
(previously singleton) with theRCTDevSettings
instance, which has access to theRCTBundleManager
, which contains the specified configuration object. The connection is now established in theRCTDevSettings initialize
method, called after the bundle manager is set by invoking the newstartWithBundleManager
method on theRCTPackagerConnection
.The
RCTCustomBundleConfiguration
allows only for eitherbundleFilePath
or(packagerServerScheme, packagerServerHost)
to be set by defining appropriate initializers.The logic for creating bundle URL query items is extracted to a separate
createJSBundleURLQuery
method and is used byRCTBundleManager
to set the configuredpackagerServerHost
andpackagerServerScheme
. If the configuration is not defined, thegetBundleURL
method returns the result of the passedfallbackURLProvider
.The
bundleFilePath
should be created with[NSURL fileURLWithPath:<path>]
, as otherwise the HMR client is created and fails ungracefully. The check is added in thegetBundle
method to log the error beforehand:When the
bundleFilePath
is set in theRCTCustomBundleConfiguration
theConnect to Metro...
message shouldn't be suggested.Changelog:
[IOS][ADDED] - Add new
RCTCustomBundleConfiguration
for modifying bundle URL onRCTReactNativeFactory
.Test Plan:
Tested changing
packagerServerHost
from theAppDelegate
by re-creating the React Native instance with updatedRCTCustomBundleConfiguration
. I've run two Metro instances, each serving a different JS bundle (changed background) on8081
and8082
ports. The nativeRestart RN:<current port>
button on top of the screen toggles between ports (used in bundle configuration) and re-creates connections. Tested withRCT_DEV
set to true and false.port-toggle-recording.mov
For setting bundle source from a file, I've generated bundle with a blue background and created a custom bundle configuration using
initWithBundleFilePath
. I've run the app without starting Metro:code:
AppDelegate.mm
AppDelegate.h