Skip to content
This repository was archived by the owner on Jul 28, 2022. It is now read-only.

Commit 10ec82b

Browse files
authored
feat(plugin): add ios expo plugin (#72)
* feat(plugin): add ios expo plugin * ✏️ (expo) rename iOSApiKey to iosApiKey * ♻️ (expo) import Batch earlier in AppDelegate * 📝 (expo) update android installation doc * 📝 (expo) add ios installation doc * 📝 (expo) update ios warning on AppDelegate * 📝 (expo) delete ios comment about previous version
1 parent bd1374d commit 10ec82b

9 files changed

+441
-20
lines changed

plugin/src/__tests__/withReactNativeBatchAppBuildGradle.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import {
77
describe(pushDependencies, () => {
88
it('should push depedencies in the App ProjetGradle file', () => {
99
const result = pushDependencies(buildGradleFixture, {
10-
apiKey: 'FAKE_API_KEY',
10+
iosApiKey: 'FAKE_IOS_API_KEY',
11+
androidApiKey: 'FAKE_ANDROID_API_KEY',
1112
});
1213

1314
expect(result).toEqual(buildGradleExpectedFixture);
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { modifyAppDelegate } from '../withReactNativeBatchAppDelegate';
2+
import {
3+
appDelegateExpectedFixture,
4+
appDelegateFixture,
5+
} from '../fixtures/appDelegate';
6+
7+
describe(modifyAppDelegate, () => {
8+
it('should modify the AppDelegate', () => {
9+
const result = modifyAppDelegate(appDelegateFixture);
10+
11+
expect(result).toEqual(appDelegateExpectedFixture);
12+
});
13+
});

plugin/src/fixtures/appDelegate.ts

Lines changed: 319 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,319 @@
1+
export const appDelegateFixture = `#import "AppDelegate.h"
2+
3+
#if defined(EX_DEV_MENU_ENABLED)
4+
@import EXDevMenu;
5+
#endif
6+
7+
#if defined(EX_DEV_LAUNCHER_ENABLED)
8+
#include <EXDevLauncher/EXDevLauncherController.h>
9+
#import <EXUpdates/EXUpdatesDevLauncherController.h>
10+
#endif
11+
12+
#import <React/RCTBridge.h>
13+
#import <React/RCTBundleURLProvider.h>
14+
#import <React/RCTRootView.h>
15+
#import <React/RCTLinkingManager.h>
16+
17+
#import <UMCore/UMModuleRegistry.h>
18+
#import <UMReactNativeAdapter/UMNativeModulesProxy.h>
19+
#import <UMReactNativeAdapter/UMModuleRegistryAdapter.h>
20+
#import <EXSplashScreen/EXSplashScreenService.h>
21+
#import <UMCore/UMModuleRegistryProvider.h>
22+
23+
#if defined(FB_SONARKIT_ENABLED) && __has_include(<FlipperKit/FlipperClient.h>)
24+
#import <FlipperKit/FlipperClient.h>
25+
#import <FlipperKitLayoutPlugin/FlipperKitLayoutPlugin.h>
26+
#import <FlipperKitUserDefaultsPlugin/FKUserDefaultsPlugin.h>
27+
#import <FlipperKitNetworkPlugin/FlipperKitNetworkPlugin.h>
28+
#import <SKIOSNetworkPlugin/SKIOSNetworkAdapter.h>
29+
#import <FlipperKitReactPlugin/FlipperKitReactPlugin.h>
30+
31+
static void InitializeFlipper(UIApplication *application) {
32+
FlipperClient *client = [FlipperClient sharedClient];
33+
SKDescriptorMapper *layoutDescriptorMapper = [[SKDescriptorMapper alloc] initWithDefaults];
34+
[client addPlugin:[[FlipperKitLayoutPlugin alloc] initWithRootNode:application withDescriptorMapper:layoutDescriptorMapper]];
35+
[client addPlugin:[[FKUserDefaultsPlugin alloc] initWithSuiteName:nil]];
36+
[client addPlugin:[FlipperKitReactPlugin new]];
37+
[client addPlugin:[[FlipperKitNetworkPlugin alloc] initWithNetworkAdapter:[SKIOSNetworkAdapter new]]];
38+
[client start];
39+
}
40+
#endif
41+
42+
@interface AppDelegate () <RCTBridgeDelegate>
43+
44+
@property (nonatomic, strong) UMModuleRegistryAdapter *moduleRegistryAdapter;
45+
@property (nonatomic, strong) NSDictionary *launchOptions;
46+
47+
@end
48+
49+
@implementation AppDelegate
50+
51+
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
52+
{
53+
#if defined(FB_SONARKIT_ENABLED) && __has_include(<FlipperKit/FlipperClient.h>)
54+
InitializeFlipper(application);
55+
#endif
56+
57+
self.moduleRegistryAdapter = [[UMModuleRegistryAdapter alloc] initWithModuleRegistryProvider:[[UMModuleRegistryProvider alloc] init]];
58+
self.launchOptions = launchOptions;
59+
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
60+
#ifdef DEBUG
61+
#if defined(EX_DEV_LAUNCHER_ENABLED)
62+
EXDevLauncherController *controller = [EXDevLauncherController sharedInstance];
63+
controller.updatesInterface = [EXUpdatesDevLauncherController sharedInstance];
64+
[controller startWithWindow:self.window delegate:(id<EXDevLauncherControllerDelegate>)self launchOptions:launchOptions];
65+
#else
66+
[self initializeReactNativeApp];
67+
#endif
68+
#else
69+
EXUpdatesAppController *controller = [EXUpdatesAppController sharedInstance];
70+
controller.delegate = self;
71+
[controller startAndShowLaunchScreen:self.window];
72+
#endif
73+
74+
[super application:application didFinishLaunchingWithOptions:launchOptions];
75+
76+
return YES;
77+
}
78+
79+
- (RCTBridge *)initializeReactNativeApp
80+
{
81+
#if defined(EX_DEV_LAUNCHER_ENABLED)
82+
NSDictionary *launchOptions = [EXDevLauncherController.sharedInstance getLaunchOptions];
83+
#else
84+
NSDictionary *launchOptions = self.launchOptions;
85+
#endif
86+
87+
RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
88+
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@"main" initialProperties:nil];
89+
rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];
90+
91+
UIViewController *rootViewController = [UIViewController new];
92+
rootViewController.view = rootView;
93+
self.window.rootViewController = rootViewController;
94+
[self.window makeKeyAndVisible];
95+
96+
return bridge;
97+
}
98+
99+
- (NSArray<id<RCTBridgeModule>> *)extraModulesForBridge:(RCTBridge *)bridge
100+
{
101+
NSArray<id<RCTBridgeModule>> *extraModules = [_moduleRegistryAdapter extraModulesForBridge:bridge];
102+
// If you'd like to export some custom RCTBridgeModules that are not Expo modules, add them here!
103+
return extraModules;
104+
}
105+
106+
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge {
107+
#ifdef DEBUG
108+
#if defined(EX_DEV_LAUNCHER_ENABLED)
109+
return [[EXDevLauncherController sharedInstance] sourceUrl];
110+
#else
111+
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
112+
#endif
113+
#else
114+
return [[EXUpdatesAppController sharedInstance] launchAssetUrl];
115+
#endif
116+
}
117+
118+
- (void)appController:(EXUpdatesAppController *)appController didStartWithSuccess:(BOOL)success {
119+
appController.bridge = [self initializeReactNativeApp];
120+
EXSplashScreenService *splashScreenService = (EXSplashScreenService *)[UMModuleRegistryProvider getSingletonModuleForClass:[EXSplashScreenService class]];
121+
[splashScreenService showSplashScreenFor:self.window.rootViewController];
122+
}
123+
124+
// Linking API
125+
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
126+
#if defined(EX_DEV_LAUNCHER_ENABLED)
127+
if ([EXDevLauncherController.sharedInstance onDeepLink:url options:options]) {
128+
return true;
129+
}
130+
#endif
131+
return [RCTLinkingManager application:application openURL:url options:options];
132+
}
133+
134+
// Universal Links
135+
- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler {
136+
return [RCTLinkingManager application:application
137+
continueUserActivity:userActivity
138+
restorationHandler:restorationHandler];
139+
}
140+
141+
@end
142+
143+
#if defined(EX_DEV_LAUNCHER_ENABLED)
144+
@implementation AppDelegate (EXDevLauncherControllerDelegate)
145+
146+
- (void)devLauncherController:(EXDevLauncherController *)developmentClientController
147+
didStartWithSuccess:(BOOL)success
148+
{
149+
developmentClientController.appBridge = [self initializeReactNativeApp];
150+
EXSplashScreenService *splashScreenService = (EXSplashScreenService *)[UMModuleRegistryProvider getSingletonModuleForClass:[EXSplashScreenService class]];
151+
[splashScreenService showSplashScreenFor:self.window.rootViewController];
152+
}
153+
154+
@end
155+
#endif
156+
`;
157+
158+
export const appDelegateExpectedFixture = `#import "AppDelegate.h"
159+
160+
#import <RNBatchPush/RNBatch.h>
161+
162+
#if defined(EX_DEV_MENU_ENABLED)
163+
@import EXDevMenu;
164+
#endif
165+
166+
#if defined(EX_DEV_LAUNCHER_ENABLED)
167+
#include <EXDevLauncher/EXDevLauncherController.h>
168+
#import <EXUpdates/EXUpdatesDevLauncherController.h>
169+
#endif
170+
171+
#import <React/RCTBridge.h>
172+
#import <React/RCTBundleURLProvider.h>
173+
#import <React/RCTRootView.h>
174+
#import <React/RCTLinkingManager.h>
175+
176+
#import <UMCore/UMModuleRegistry.h>
177+
#import <UMReactNativeAdapter/UMNativeModulesProxy.h>
178+
#import <UMReactNativeAdapter/UMModuleRegistryAdapter.h>
179+
#import <EXSplashScreen/EXSplashScreenService.h>
180+
#import <UMCore/UMModuleRegistryProvider.h>
181+
182+
#if defined(FB_SONARKIT_ENABLED) && __has_include(<FlipperKit/FlipperClient.h>)
183+
#import <FlipperKit/FlipperClient.h>
184+
#import <FlipperKitLayoutPlugin/FlipperKitLayoutPlugin.h>
185+
#import <FlipperKitUserDefaultsPlugin/FKUserDefaultsPlugin.h>
186+
#import <FlipperKitNetworkPlugin/FlipperKitNetworkPlugin.h>
187+
#import <SKIOSNetworkPlugin/SKIOSNetworkAdapter.h>
188+
#import <FlipperKitReactPlugin/FlipperKitReactPlugin.h>
189+
190+
static void InitializeFlipper(UIApplication *application) {
191+
FlipperClient *client = [FlipperClient sharedClient];
192+
SKDescriptorMapper *layoutDescriptorMapper = [[SKDescriptorMapper alloc] initWithDefaults];
193+
[client addPlugin:[[FlipperKitLayoutPlugin alloc] initWithRootNode:application withDescriptorMapper:layoutDescriptorMapper]];
194+
[client addPlugin:[[FKUserDefaultsPlugin alloc] initWithSuiteName:nil]];
195+
[client addPlugin:[FlipperKitReactPlugin new]];
196+
[client addPlugin:[[FlipperKitNetworkPlugin alloc] initWithNetworkAdapter:[SKIOSNetworkAdapter new]]];
197+
[client start];
198+
}
199+
#endif
200+
201+
@interface AppDelegate () <RCTBridgeDelegate>
202+
203+
@property (nonatomic, strong) UMModuleRegistryAdapter *moduleRegistryAdapter;
204+
@property (nonatomic, strong) NSDictionary *launchOptions;
205+
206+
@end
207+
208+
@implementation AppDelegate
209+
210+
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
211+
{
212+
[RNBatch start];
213+
[BatchUNUserNotificationCenterDelegate registerAsDelegate];
214+
[BatchUNUserNotificationCenterDelegate sharedInstance].showForegroundNotifications = true;
215+
216+
#if defined(FB_SONARKIT_ENABLED) && __has_include(<FlipperKit/FlipperClient.h>)
217+
InitializeFlipper(application);
218+
#endif
219+
220+
self.moduleRegistryAdapter = [[UMModuleRegistryAdapter alloc] initWithModuleRegistryProvider:[[UMModuleRegistryProvider alloc] init]];
221+
self.launchOptions = launchOptions;
222+
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
223+
#ifdef DEBUG
224+
#if defined(EX_DEV_LAUNCHER_ENABLED)
225+
EXDevLauncherController *controller = [EXDevLauncherController sharedInstance];
226+
controller.updatesInterface = [EXUpdatesDevLauncherController sharedInstance];
227+
[controller startWithWindow:self.window delegate:(id<EXDevLauncherControllerDelegate>)self launchOptions:launchOptions];
228+
#else
229+
[self initializeReactNativeApp];
230+
#endif
231+
#else
232+
EXUpdatesAppController *controller = [EXUpdatesAppController sharedInstance];
233+
controller.delegate = self;
234+
[controller startAndShowLaunchScreen:self.window];
235+
#endif
236+
237+
[super application:application didFinishLaunchingWithOptions:launchOptions];
238+
239+
return YES;
240+
}
241+
242+
- (RCTBridge *)initializeReactNativeApp
243+
{
244+
#if defined(EX_DEV_LAUNCHER_ENABLED)
245+
NSDictionary *launchOptions = [EXDevLauncherController.sharedInstance getLaunchOptions];
246+
#else
247+
NSDictionary *launchOptions = self.launchOptions;
248+
#endif
249+
250+
RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
251+
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@"main" initialProperties:nil];
252+
rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];
253+
254+
UIViewController *rootViewController = [UIViewController new];
255+
rootViewController.view = rootView;
256+
self.window.rootViewController = rootViewController;
257+
[self.window makeKeyAndVisible];
258+
259+
return bridge;
260+
}
261+
262+
- (NSArray<id<RCTBridgeModule>> *)extraModulesForBridge:(RCTBridge *)bridge
263+
{
264+
NSArray<id<RCTBridgeModule>> *extraModules = [_moduleRegistryAdapter extraModulesForBridge:bridge];
265+
// If you'd like to export some custom RCTBridgeModules that are not Expo modules, add them here!
266+
return extraModules;
267+
}
268+
269+
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge {
270+
#ifdef DEBUG
271+
#if defined(EX_DEV_LAUNCHER_ENABLED)
272+
return [[EXDevLauncherController sharedInstance] sourceUrl];
273+
#else
274+
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
275+
#endif
276+
#else
277+
return [[EXUpdatesAppController sharedInstance] launchAssetUrl];
278+
#endif
279+
}
280+
281+
- (void)appController:(EXUpdatesAppController *)appController didStartWithSuccess:(BOOL)success {
282+
appController.bridge = [self initializeReactNativeApp];
283+
EXSplashScreenService *splashScreenService = (EXSplashScreenService *)[UMModuleRegistryProvider getSingletonModuleForClass:[EXSplashScreenService class]];
284+
[splashScreenService showSplashScreenFor:self.window.rootViewController];
285+
}
286+
287+
// Linking API
288+
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
289+
#if defined(EX_DEV_LAUNCHER_ENABLED)
290+
if ([EXDevLauncherController.sharedInstance onDeepLink:url options:options]) {
291+
return true;
292+
}
293+
#endif
294+
return [RCTLinkingManager application:application openURL:url options:options];
295+
}
296+
297+
// Universal Links
298+
- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler {
299+
return [RCTLinkingManager application:application
300+
continueUserActivity:userActivity
301+
restorationHandler:restorationHandler];
302+
}
303+
304+
@end
305+
306+
#if defined(EX_DEV_LAUNCHER_ENABLED)
307+
@implementation AppDelegate (EXDevLauncherControllerDelegate)
308+
309+
- (void)devLauncherController:(EXDevLauncherController *)developmentClientController
310+
didStartWithSuccess:(BOOL)success
311+
{
312+
developmentClientController.appBridge = [self initializeReactNativeApp];
313+
EXSplashScreenService *splashScreenService = (EXSplashScreenService *)[UMModuleRegistryProvider getSingletonModuleForClass:[EXSplashScreenService class]];
314+
[splashScreenService showSplashScreenFor:self.window.rootViewController];
315+
}
316+
317+
@end
318+
#endif
319+
`;

plugin/src/fixtures/buildGradle.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ android {
154154
targetSdkVersion rootProject.ext.targetSdkVersion
155155
versionCode 1
156156
versionName "1.0"
157-
resValue "string", "BATCH_API_KEY", "FAKE_API_KEY"
157+
resValue "string", "BATCH_API_KEY", "FAKE_ANDROID_API_KEY"
158158
}
159159
splits {
160160
abi {

plugin/src/withReactNativeBatch.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,24 @@ import {
77
import { withReactNativeBatchMainActivity } from './withReactNativeBatchMainActivity';
88
import { withReactNativeBatchAppBuildGradle } from './withReactNativeBatchAppBuildGradle';
99
import { withReactNativeBatchProjectBuildGradle } from './withReactNativeBatchProjectBuildGradle';
10+
import { withReactNativeBatchInfoPlist } from './withReactNativeBatchInfoPlist';
11+
import { withReactNativeBatchAppDelegate } from './withReactNativeBatchAppDelegate';
1012

11-
export type Props = { apiKey: string };
13+
export type Props = { androidApiKey: string; iosApiKey: string };
1214
/**
1315
* Apply react-native-batch configuration for Expo SDK 42 projects.
1416
*/
1517
const withReactNativeBatch: ConfigPlugin<Props | void> = (config, props) => {
16-
const _props = props || { apiKey: '' };
18+
const _props = props || { androidApiKey: '', iosApiKey: '' };
1719

1820
let newConfig = withGoogleServicesFile(config);
1921
newConfig = withClassPath(newConfig);
2022
newConfig = withApplyPlugin(newConfig);
2123
newConfig = withReactNativeBatchAppBuildGradle(newConfig, _props);
2224
newConfig = withReactNativeBatchMainActivity(newConfig);
2325
newConfig = withReactNativeBatchProjectBuildGradle(newConfig);
26+
newConfig = withReactNativeBatchInfoPlist(newConfig, _props);
27+
newConfig = withReactNativeBatchAppDelegate(newConfig);
2428
// Return the modified config.
2529
return newConfig;
2630
};

plugin/src/withReactNativeBatchAppBuildGradle.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export const pushDependencies = (contents: string, props: Props): string => {
2020
newContents =
2121
start +
2222
defaultConfigContents[0] +
23-
` resValue "string", "BATCH_API_KEY", "${props.apiKey}"` +
23+
` resValue "string", "BATCH_API_KEY", "${props.androidApiKey}"` +
2424
'\n ' +
2525
end;
2626
}

0 commit comments

Comments
 (0)