Open rocketraman opened 1 week ago
And an update... yes, it appears this approach is not compatible with Expo... returning self
seems to break Expo's internal loading mechanisms. So I'd love to understand what is going on here.
I updated the code to reflect the latest initial setup of RN 0.74.0 setup (see here).
Before that, we could initialize the bridge ourself and assign the delegate using
RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
see AppDelegate.mm
at this point
But the new setup did not include manually initializing the bridge, probably to simplify the setup with the new architecture. Because of this I looked for another way to set the bridge delegate and found that overwriting createBridgeWithDelegate
was the best option.
But I am happy to use a different solution. If self
is the problem we could also create a separate class for the bridge delegate that contains extraModulesForBridge
.
But I am happy to use a different solution. If
self
is the problem we could also create a separate class for the bridge delegate that containsextraModulesForBridge
.
This approach seems to work for me. To be specific, I did this:
// CustomBridgeDelegate.h
#import <Foundation/Foundation.h>
#import <React/RCTBridgeDelegate.h>
#import "AppDelegate.h"
@interface CustomBridgeDelegate : NSObject<RCTBridgeDelegate>
- (instancetype)initWithDelegates:(AppDelegate *)appDelegate
originalDelegate:(id<RCTBridgeDelegate>)originalDelegate;
@end
// CustomBridgeDelegate.mm
#import "CustomBridgeDelegate.h"
#import <MobileSdk/MobileSdk.h>
#import <Expo/Expo.h>
#import "AppDelegate.h"
#import <React/RCTLinkingManager.h>
#import <React/RCTBridgeDelegate.h>
@interface CustomBridgeDelegate ()
@property (nonatomic, weak) AppDelegate* appDelegate;
@property (nonatomic, weak) id<RCTBridgeDelegate> originalDelegate;
@end
@implementation CustomBridgeDelegate
- (instancetype)initWithDelegates:(AppDelegate *)appDelegate
originalDelegate:(id<RCTBridgeDelegate>)originalDelegate {
self = [super init];
if (self) {
self.appDelegate = appDelegate;
self.originalDelegate = originalDelegate;
}
return self;
}
#pragma mark - RCTBridgeDelegate Methods
- (NSArray<id<RCTBridgeModule>> *)extraModulesForBridge:(RCTBridge *)bridge {
// NSLog(@"[CustomBridgeDelegate] extraModulesForBridge called");
NSArray<id<RCTBridgeModule>> *rnNativeModules = [self.appDelegate.mobileSdk createNativeModules];
NSArray<id<RCTBridgeModule>> *rnViewManagers = @[];
NSArray *customModules = [rnNativeModules arrayByAddingObjectsFromArray:rnViewManagers];
// Get modules from the original delegate, if any
NSArray *originalModules = nil;
if ([self.originalDelegate respondsToSelector:@selector(extraModulesForBridge:)]) {
originalModules = [self.originalDelegate extraModulesForBridge:bridge];
}
// Combine both arrays
NSMutableArray *allModules = [NSMutableArray arrayWithArray:customModules];
if (originalModules) {
[allModules addObjectsFromArray:originalModules];
}
return allModules;
}
// Forward sourceURLForBridge to the original delegate (an Expo class)
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge {
if ([self.originalDelegate respondsToSelector:@selector(sourceURLForBridge:)]) {
return [self.originalDelegate sourceURLForBridge:bridge];
}
NSLog(@"[CustomBridgeDelegate] Warning: originalDelegate does not implement sourceURLForBridge");
return nil;
}
@end
and then in AppDelegate.mm
:
- (RCTBridge *)createBridgeWithDelegate:(id<RCTBridgeDelegate>)delegate launchOptions:(NSDictionary *)launchOptions
{
// this does not hook extraModulesForBridge properly, and passing :this instead of :delegate does, but
// does not hook the sourceURLForBridge properly -- create a CustomBridgeDelegate that implements both of these
// return [[RCTBridge alloc] initWithDelegate:delegate launchOptions:launchOptions];
// Create the custom delegate, passing this and the delegate
CustomBridgeDelegate *customDelegate = [[CustomBridgeDelegate alloc] initWithDelegates:self
originalDelegate:(RCTAppDelegate *)delegate];
// Initialize the bridge with the custom delegate
return [[RCTBridge alloc] initWithDelegate:customDelegate launchOptions:launchOptions];
}
It feels like a hack rather than a proper solution though -- I'm not really sure I understand how the React Native guys intended for the RCTBridgeDelegate
to be customized. RCTBridgeDelegate
is a protocol, but an instance of it is passed to RCTAppDelegate.mm
, and I can't find the code in React Native that actually creates this instance -- the closest seems to be a call in RCTRootViewFactory.mm
that calls it with self
. Expo is customizing this delegate somehow as well.
The example app overrides
createBridgeWithDelegate
to passself
rather thandelegate
. This seems to be necessary in React 0.74.5, otherwise theextraModulesForBridge
method is never called.Is this an upstream bug that we are working around? Might there be any unintended side-effects of this?