Skip to content
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

fix(app, expo): Update iOS AppDelegate plugin to work with Expo SDK 43 #5796

Merged
merged 3 commits into from
Oct 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 102 additions & 3 deletions packages/app/plugin/__tests__/__snapshots__/iosPlugin.test.ts.snap
Original file line number Diff line number Diff line change
@@ -1,7 +1,104 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Config Plugin iOS Tests tests changes made to AppDelegate.m 1`] = `
"#import \\"AppDelegate.h\\"
exports[`Config Plugin iOS Tests tests changes made to AppDelegate.m (SDK 43+) 1`] = `
"// This AppDelegate template is used in Expo SDK 43 and newer
// It is (nearly) identical to the pure template used when
// creating a bare React Native app (without Expo)

#import \\"AppDelegate.h\\"
@import Firebase;

#import <React/RCTBridge.h>
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>
#import <React/RCTLinkingManager.h>
#import <React/RCTConvert.h>

#if defined(FB_SONARKIT_ENABLED) && __has_include(<FlipperKit/FlipperClient.h>)
#import <FlipperKit/FlipperClient.h>
#import <FlipperKitLayoutPlugin/FlipperKitLayoutPlugin.h>
#import <FlipperKitUserDefaultsPlugin/FKUserDefaultsPlugin.h>
#import <FlipperKitNetworkPlugin/FlipperKitNetworkPlugin.h>
#import <SKIOSNetworkPlugin/SKIOSNetworkAdapter.h>
#import <FlipperKitReactPlugin/FlipperKitReactPlugin.h>

static void InitializeFlipper(UIApplication *application) {
FlipperClient *client = [FlipperClient sharedClient];
SKDescriptorMapper *layoutDescriptorMapper = [[SKDescriptorMapper alloc] initWithDefaults];
[client addPlugin:[[FlipperKitLayoutPlugin alloc] initWithRootNode:application withDescriptorMapper:layoutDescriptorMapper]];
[client addPlugin:[[FKUserDefaultsPlugin alloc] initWithSuiteName:nil]];
[client addPlugin:[FlipperKitReactPlugin new]];
[client addPlugin:[[FlipperKitNetworkPlugin alloc] initWithNetworkAdapter:[SKIOSNetworkAdapter new]]];
[client start];
}
#endif

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
#if defined(FB_SONARKIT_ENABLED) && __has_include(<FlipperKit/FlipperClient.h>)
InitializeFlipper(application);
#endif

// @generated begin @react-native-firebase/app-didFinishLaunchingWithOptions - expo prebuild (DO NOT MODIFY) sync-ecd111c37e49fdd1ed6354203cd6b1e2a38cccda
[FIRApp configure];
// @generated end @react-native-firebase/app-didFinishLaunchingWithOptions
RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@\\"main\\" initialProperties:nil];
id rootViewBackgroundColor = [[NSBundle mainBundle] objectForInfoDictionaryKey:@\\"RCTRootViewBackgroundColor\\"];
if (rootViewBackgroundColor != nil) {
rootView.backgroundColor = [RCTConvert UIColor:rootViewBackgroundColor];
} else {
rootView.backgroundColor = [UIColor whiteColor];
}

self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
UIViewController *rootViewController = [UIViewController new];
rootViewController.view = rootView;
self.window.rootViewController = rootViewController;
[self.window makeKeyAndVisible];

[super application:application didFinishLaunchingWithOptions:launchOptions];

return YES;
}

- (NSArray<id<RCTBridgeModule>> *)extraModulesForBridge:(RCTBridge *)bridge
{
// If you'd like to export some custom RCTBridgeModules, add them here!
return @[];
}

- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge {
#ifdef DEBUG
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@\\"index\\" fallbackResource:nil];
#else
return [[NSBundle mainBundle] URLForResource:@\\"main\\" withExtension:@\\"jsbundle\\"];
#endif
}

// Linking API
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
return [RCTLinkingManager application:application openURL:url options:options];
}

// Universal Links
- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler {
return [RCTLinkingManager application:application
continueUserActivity:userActivity
restorationHandler:restorationHandler];
}

@end
"
`;

exports[`Config Plugin iOS Tests tests changes made to old AppDelegate.m (SDK 42) 1`] = `
"// This AppDelegate prebuild template is used in Expo SDK 42 and older
// It expects the old react-native-unimodules architecture (UM* prefix)

#import \\"AppDelegate.h\\"
@import Firebase;

#import <React/RCTBridge.h>
Expand Down Expand Up @@ -49,7 +146,9 @@ static void InitializeFlipper(UIApplication *application) {
InitializeFlipper(application);
#endif

[FIRApp configure];
// @generated begin @react-native-firebase/app-didFinishLaunchingWithOptions - expo prebuild (DO NOT MODIFY) sync-ecd111c37e49fdd1ed6354203cd6b1e2a38cccda
[FIRApp configure];
// @generated end @react-native-firebase/app-didFinishLaunchingWithOptions
self.moduleRegistryAdapter = [[UMModuleRegistryAdapter alloc] initWithModuleRegistryProvider:[[UMModuleRegistryProvider alloc] init]];
self.launchOptions = launchOptions;
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
Expand Down
86 changes: 86 additions & 0 deletions packages/app/plugin/__tests__/fixtures/AppDelegate_bare_sdk43.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// This AppDelegate template is used in Expo SDK 43 and newer
// It is (nearly) identical to the pure template used when
// creating a bare React Native app (without Expo)

#import "AppDelegate.h"

#import <React/RCTBridge.h>
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>
#import <React/RCTLinkingManager.h>
#import <React/RCTConvert.h>

#if defined(FB_SONARKIT_ENABLED) && __has_include(<FlipperKit/FlipperClient.h>)
#import <FlipperKit/FlipperClient.h>
#import <FlipperKitLayoutPlugin/FlipperKitLayoutPlugin.h>
#import <FlipperKitUserDefaultsPlugin/FKUserDefaultsPlugin.h>
#import <FlipperKitNetworkPlugin/FlipperKitNetworkPlugin.h>
#import <SKIOSNetworkPlugin/SKIOSNetworkAdapter.h>
#import <FlipperKitReactPlugin/FlipperKitReactPlugin.h>

static void InitializeFlipper(UIApplication *application) {
FlipperClient *client = [FlipperClient sharedClient];
SKDescriptorMapper *layoutDescriptorMapper = [[SKDescriptorMapper alloc] initWithDefaults];
[client addPlugin:[[FlipperKitLayoutPlugin alloc] initWithRootNode:application withDescriptorMapper:layoutDescriptorMapper]];
[client addPlugin:[[FKUserDefaultsPlugin alloc] initWithSuiteName:nil]];
[client addPlugin:[FlipperKitReactPlugin new]];
[client addPlugin:[[FlipperKitNetworkPlugin alloc] initWithNetworkAdapter:[SKIOSNetworkAdapter new]]];
[client start];
}
#endif

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
#if defined(FB_SONARKIT_ENABLED) && __has_include(<FlipperKit/FlipperClient.h>)
InitializeFlipper(application);
#endif

RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@"main" initialProperties:nil];
id rootViewBackgroundColor = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"RCTRootViewBackgroundColor"];
if (rootViewBackgroundColor != nil) {
rootView.backgroundColor = [RCTConvert UIColor:rootViewBackgroundColor];
} else {
rootView.backgroundColor = [UIColor whiteColor];
}

self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
UIViewController *rootViewController = [UIViewController new];
rootViewController.view = rootView;
self.window.rootViewController = rootViewController;
[self.window makeKeyAndVisible];

[super application:application didFinishLaunchingWithOptions:launchOptions];

return YES;
}

- (NSArray<id<RCTBridgeModule>> *)extraModulesForBridge:(RCTBridge *)bridge
{
// If you'd like to export some custom RCTBridgeModules, add them here!
return @[];
}

- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge {
#ifdef DEBUG
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
#else
return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
#endif
}

// Linking API
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
return [RCTLinkingManager application:application openURL:url options:options];
}

// Universal Links
- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler {
return [RCTLinkingManager application:application
continueUserActivity:userActivity
restorationHandler:restorationHandler];
}

@end
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// This AppDelegate prebuild template is used in Expo SDK 42 and older
// It expects the old react-native-unimodules architecture (UM* prefix)

#import "AppDelegate.h"

#import <React/RCTBridge.h>
Expand Down
15 changes: 13 additions & 2 deletions packages/app/plugin/__tests__/iosPlugin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,22 @@ import path from 'path';
import { modifyObjcAppDelegate } from '../src/ios/appDelegate';

describe('Config Plugin iOS Tests', function () {
it('tests changes made to AppDelegate.m', async function () {
const appDelegate = await fs.readFile(path.join(__dirname, './fixtures/AppDelegate.m'), {
it('tests changes made to old AppDelegate.m (SDK 42)', async function () {
const appDelegate = await fs.readFile(path.join(__dirname, './fixtures/AppDelegate_sdk42.m'), {
encoding: 'utf8',
});
const result = modifyObjcAppDelegate(appDelegate);
expect(result).toMatchSnapshot();
});

it('tests changes made to AppDelegate.m (SDK 43+)', async function () {
const appDelegate = await fs.readFile(
path.join(__dirname, './fixtures/AppDelegate_bare_sdk43.m'),
{
encoding: 'utf8',
},
);
const result = modifyObjcAppDelegate(appDelegate);
expect(result).toMatchSnapshot();
});
});
25 changes: 16 additions & 9 deletions packages/app/plugin/src/ios/appDelegate.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { ConfigPlugin, IOSConfig, withDangerousMod } from '@expo/config-plugins';
import { mergeContents } from '@expo/config-plugins/build/utils/generateCode';
import fs from 'fs';

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

export function modifyObjcAppDelegate(contents: string): string {
// Add import
Expand All @@ -13,17 +17,20 @@ export function modifyObjcAppDelegate(contents: string): string {
);
}

// Add invocation
if (!contents.includes(methodInvocationBlock)) {
// self.moduleRegistryAdapter = [[UMModuleRegistryAdapter alloc]
contents = contents.replace(
/self\.moduleRegistryAdapter = \[\[UMModuleRegistryAdapter alloc\]/g,
`${methodInvocationBlock}
self.moduleRegistryAdapter = [[UMModuleRegistryAdapter alloc]`,
);
// To avoid potential issues with existing changes from older plugin versions
if (contents.includes(methodInvocationBlock)) {
return contents;
}

return contents;
// Add invocation
return mergeContents({
tag: '@react-native-firebase/app-didFinishLaunchingWithOptions',
src: contents,
newSrc: methodInvocationBlock,
anchor: methodInvocationLineMatcher,
offset: 0, // new line will be inserted right before matched anchor
comment: '//',
}).contents;
}

export const withFirebaseAppDelegate: ConfigPlugin = config => {
Expand Down