Skip to content

Commit d67c3b9

Browse files
authored
fix(app, expo): update iOS AppDelegate plugin to work with Expo SDK 43 (#5796)
* [app][expo] Updated iOS AppDelegate plugin for SDK 43 * Updated tests and snapshots * Skip merging when older plugin already applied To make the update seamless
1 parent 9c66f37 commit d67c3b9

File tree

5 files changed

+220
-14
lines changed

5 files changed

+220
-14
lines changed

packages/app/plugin/__tests__/__snapshots__/iosPlugin.test.ts.snap

Lines changed: 102 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,104 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3-
exports[`Config Plugin iOS Tests tests changes made to AppDelegate.m 1`] = `
4-
"#import \\"AppDelegate.h\\"
3+
exports[`Config Plugin iOS Tests tests changes made to AppDelegate.m (SDK 43+) 1`] = `
4+
"// This AppDelegate template is used in Expo SDK 43 and newer
5+
// It is (nearly) identical to the pure template used when
6+
// creating a bare React Native app (without Expo)
7+
8+
#import \\"AppDelegate.h\\"
9+
@import Firebase;
10+
11+
#import <React/RCTBridge.h>
12+
#import <React/RCTBundleURLProvider.h>
13+
#import <React/RCTRootView.h>
14+
#import <React/RCTLinkingManager.h>
15+
#import <React/RCTConvert.h>
16+
17+
#if defined(FB_SONARKIT_ENABLED) && __has_include(<FlipperKit/FlipperClient.h>)
18+
#import <FlipperKit/FlipperClient.h>
19+
#import <FlipperKitLayoutPlugin/FlipperKitLayoutPlugin.h>
20+
#import <FlipperKitUserDefaultsPlugin/FKUserDefaultsPlugin.h>
21+
#import <FlipperKitNetworkPlugin/FlipperKitNetworkPlugin.h>
22+
#import <SKIOSNetworkPlugin/SKIOSNetworkAdapter.h>
23+
#import <FlipperKitReactPlugin/FlipperKitReactPlugin.h>
24+
25+
static void InitializeFlipper(UIApplication *application) {
26+
FlipperClient *client = [FlipperClient sharedClient];
27+
SKDescriptorMapper *layoutDescriptorMapper = [[SKDescriptorMapper alloc] initWithDefaults];
28+
[client addPlugin:[[FlipperKitLayoutPlugin alloc] initWithRootNode:application withDescriptorMapper:layoutDescriptorMapper]];
29+
[client addPlugin:[[FKUserDefaultsPlugin alloc] initWithSuiteName:nil]];
30+
[client addPlugin:[FlipperKitReactPlugin new]];
31+
[client addPlugin:[[FlipperKitNetworkPlugin alloc] initWithNetworkAdapter:[SKIOSNetworkAdapter new]]];
32+
[client start];
33+
}
34+
#endif
35+
36+
@implementation AppDelegate
37+
38+
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
39+
{
40+
#if defined(FB_SONARKIT_ENABLED) && __has_include(<FlipperKit/FlipperClient.h>)
41+
InitializeFlipper(application);
42+
#endif
43+
44+
// @generated begin @react-native-firebase/app-didFinishLaunchingWithOptions - expo prebuild (DO NOT MODIFY) sync-ecd111c37e49fdd1ed6354203cd6b1e2a38cccda
45+
[FIRApp configure];
46+
// @generated end @react-native-firebase/app-didFinishLaunchingWithOptions
47+
RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
48+
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@\\"main\\" initialProperties:nil];
49+
id rootViewBackgroundColor = [[NSBundle mainBundle] objectForInfoDictionaryKey:@\\"RCTRootViewBackgroundColor\\"];
50+
if (rootViewBackgroundColor != nil) {
51+
rootView.backgroundColor = [RCTConvert UIColor:rootViewBackgroundColor];
52+
} else {
53+
rootView.backgroundColor = [UIColor whiteColor];
54+
}
55+
56+
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
57+
UIViewController *rootViewController = [UIViewController new];
58+
rootViewController.view = rootView;
59+
self.window.rootViewController = rootViewController;
60+
[self.window makeKeyAndVisible];
61+
62+
[super application:application didFinishLaunchingWithOptions:launchOptions];
63+
64+
return YES;
65+
}
66+
67+
- (NSArray<id<RCTBridgeModule>> *)extraModulesForBridge:(RCTBridge *)bridge
68+
{
69+
// If you'd like to export some custom RCTBridgeModules, add them here!
70+
return @[];
71+
}
72+
73+
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge {
74+
#ifdef DEBUG
75+
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@\\"index\\" fallbackResource:nil];
76+
#else
77+
return [[NSBundle mainBundle] URLForResource:@\\"main\\" withExtension:@\\"jsbundle\\"];
78+
#endif
79+
}
80+
81+
// Linking API
82+
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
83+
return [RCTLinkingManager application:application openURL:url options:options];
84+
}
85+
86+
// Universal Links
87+
- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler {
88+
return [RCTLinkingManager application:application
89+
continueUserActivity:userActivity
90+
restorationHandler:restorationHandler];
91+
}
92+
93+
@end
94+
"
95+
`;
96+
97+
exports[`Config Plugin iOS Tests tests changes made to old AppDelegate.m (SDK 42) 1`] = `
98+
"// This AppDelegate prebuild template is used in Expo SDK 42 and older
99+
// It expects the old react-native-unimodules architecture (UM* prefix)
100+
101+
#import \\"AppDelegate.h\\"
5102
@import Firebase;
6103
7104
#import <React/RCTBridge.h>
@@ -49,7 +146,9 @@ static void InitializeFlipper(UIApplication *application) {
49146
InitializeFlipper(application);
50147
#endif
51148
52-
[FIRApp configure];
149+
// @generated begin @react-native-firebase/app-didFinishLaunchingWithOptions - expo prebuild (DO NOT MODIFY) sync-ecd111c37e49fdd1ed6354203cd6b1e2a38cccda
150+
[FIRApp configure];
151+
// @generated end @react-native-firebase/app-didFinishLaunchingWithOptions
53152
self.moduleRegistryAdapter = [[UMModuleRegistryAdapter alloc] initWithModuleRegistryProvider:[[UMModuleRegistryProvider alloc] init]];
54153
self.launchOptions = launchOptions;
55154
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// This AppDelegate template is used in Expo SDK 43 and newer
2+
// It is (nearly) identical to the pure template used when
3+
// creating a bare React Native app (without Expo)
4+
5+
#import "AppDelegate.h"
6+
7+
#import <React/RCTBridge.h>
8+
#import <React/RCTBundleURLProvider.h>
9+
#import <React/RCTRootView.h>
10+
#import <React/RCTLinkingManager.h>
11+
#import <React/RCTConvert.h>
12+
13+
#if defined(FB_SONARKIT_ENABLED) && __has_include(<FlipperKit/FlipperClient.h>)
14+
#import <FlipperKit/FlipperClient.h>
15+
#import <FlipperKitLayoutPlugin/FlipperKitLayoutPlugin.h>
16+
#import <FlipperKitUserDefaultsPlugin/FKUserDefaultsPlugin.h>
17+
#import <FlipperKitNetworkPlugin/FlipperKitNetworkPlugin.h>
18+
#import <SKIOSNetworkPlugin/SKIOSNetworkAdapter.h>
19+
#import <FlipperKitReactPlugin/FlipperKitReactPlugin.h>
20+
21+
static void InitializeFlipper(UIApplication *application) {
22+
FlipperClient *client = [FlipperClient sharedClient];
23+
SKDescriptorMapper *layoutDescriptorMapper = [[SKDescriptorMapper alloc] initWithDefaults];
24+
[client addPlugin:[[FlipperKitLayoutPlugin alloc] initWithRootNode:application withDescriptorMapper:layoutDescriptorMapper]];
25+
[client addPlugin:[[FKUserDefaultsPlugin alloc] initWithSuiteName:nil]];
26+
[client addPlugin:[FlipperKitReactPlugin new]];
27+
[client addPlugin:[[FlipperKitNetworkPlugin alloc] initWithNetworkAdapter:[SKIOSNetworkAdapter new]]];
28+
[client start];
29+
}
30+
#endif
31+
32+
@implementation AppDelegate
33+
34+
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
35+
{
36+
#if defined(FB_SONARKIT_ENABLED) && __has_include(<FlipperKit/FlipperClient.h>)
37+
InitializeFlipper(application);
38+
#endif
39+
40+
RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
41+
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@"main" initialProperties:nil];
42+
id rootViewBackgroundColor = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"RCTRootViewBackgroundColor"];
43+
if (rootViewBackgroundColor != nil) {
44+
rootView.backgroundColor = [RCTConvert UIColor:rootViewBackgroundColor];
45+
} else {
46+
rootView.backgroundColor = [UIColor whiteColor];
47+
}
48+
49+
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
50+
UIViewController *rootViewController = [UIViewController new];
51+
rootViewController.view = rootView;
52+
self.window.rootViewController = rootViewController;
53+
[self.window makeKeyAndVisible];
54+
55+
[super application:application didFinishLaunchingWithOptions:launchOptions];
56+
57+
return YES;
58+
}
59+
60+
- (NSArray<id<RCTBridgeModule>> *)extraModulesForBridge:(RCTBridge *)bridge
61+
{
62+
// If you'd like to export some custom RCTBridgeModules, add them here!
63+
return @[];
64+
}
65+
66+
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge {
67+
#ifdef DEBUG
68+
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
69+
#else
70+
return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
71+
#endif
72+
}
73+
74+
// Linking API
75+
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
76+
return [RCTLinkingManager application:application openURL:url options:options];
77+
}
78+
79+
// Universal Links
80+
- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler {
81+
return [RCTLinkingManager application:application
82+
continueUserActivity:userActivity
83+
restorationHandler:restorationHandler];
84+
}
85+
86+
@end

packages/app/plugin/__tests__/fixtures/AppDelegate.m renamed to packages/app/plugin/__tests__/fixtures/AppDelegate_sdk42.m

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
// This AppDelegate prebuild template is used in Expo SDK 42 and older
2+
// It expects the old react-native-unimodules architecture (UM* prefix)
3+
14
#import "AppDelegate.h"
25

36
#import <React/RCTBridge.h>

packages/app/plugin/__tests__/iosPlugin.test.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,22 @@ import path from 'path';
44
import { modifyObjcAppDelegate } from '../src/ios/appDelegate';
55

66
describe('Config Plugin iOS Tests', function () {
7-
it('tests changes made to AppDelegate.m', async function () {
8-
const appDelegate = await fs.readFile(path.join(__dirname, './fixtures/AppDelegate.m'), {
7+
it('tests changes made to old AppDelegate.m (SDK 42)', async function () {
8+
const appDelegate = await fs.readFile(path.join(__dirname, './fixtures/AppDelegate_sdk42.m'), {
99
encoding: 'utf8',
1010
});
1111
const result = modifyObjcAppDelegate(appDelegate);
1212
expect(result).toMatchSnapshot();
1313
});
14+
15+
it('tests changes made to AppDelegate.m (SDK 43+)', async function () {
16+
const appDelegate = await fs.readFile(
17+
path.join(__dirname, './fixtures/AppDelegate_bare_sdk43.m'),
18+
{
19+
encoding: 'utf8',
20+
},
21+
);
22+
const result = modifyObjcAppDelegate(appDelegate);
23+
expect(result).toMatchSnapshot();
24+
});
1425
});

packages/app/plugin/src/ios/appDelegate.ts

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import { ConfigPlugin, IOSConfig, withDangerousMod } from '@expo/config-plugins';
2+
import { mergeContents } from '@expo/config-plugins/build/utils/generateCode';
23
import fs from 'fs';
34

45
const methodInvocationBlock = `[FIRApp configure];`;
6+
// https://regex101.com/r/Imm3E8/1
7+
const methodInvocationLineMatcher =
8+
/(?:(self\.|_)(\w+)\s?=\s?\[\[UMModuleRegistryAdapter alloc\])|(?:RCTBridge\s?\*\s?(\w+)\s?=\s?\[\[RCTBridge alloc\])/g;
59

610
export function modifyObjcAppDelegate(contents: string): string {
711
// Add import
@@ -13,17 +17,20 @@ export function modifyObjcAppDelegate(contents: string): string {
1317
);
1418
}
1519

16-
// Add invocation
17-
if (!contents.includes(methodInvocationBlock)) {
18-
// self.moduleRegistryAdapter = [[UMModuleRegistryAdapter alloc]
19-
contents = contents.replace(
20-
/self\.moduleRegistryAdapter = \[\[UMModuleRegistryAdapter alloc\]/g,
21-
`${methodInvocationBlock}
22-
self.moduleRegistryAdapter = [[UMModuleRegistryAdapter alloc]`,
23-
);
20+
// To avoid potential issues with existing changes from older plugin versions
21+
if (contents.includes(methodInvocationBlock)) {
22+
return contents;
2423
}
2524

26-
return contents;
25+
// Add invocation
26+
return mergeContents({
27+
tag: '@react-native-firebase/app-didFinishLaunchingWithOptions',
28+
src: contents,
29+
newSrc: methodInvocationBlock,
30+
anchor: methodInvocationLineMatcher,
31+
offset: 0, // new line will be inserted right before matched anchor
32+
comment: '//',
33+
}).contents;
2734
}
2835

2936
export const withFirebaseAppDelegate: ConfigPlugin = config => {

0 commit comments

Comments
 (0)