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

Commit

Permalink
feat(plugin): add ios expo plugin (#72)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
MaximeCrp authored Jan 25, 2022
1 parent bd1374d commit 10ec82b
Show file tree
Hide file tree
Showing 9 changed files with 441 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import {
describe(pushDependencies, () => {
it('should push depedencies in the App ProjetGradle file', () => {
const result = pushDependencies(buildGradleFixture, {
apiKey: 'FAKE_API_KEY',
iosApiKey: 'FAKE_IOS_API_KEY',
androidApiKey: 'FAKE_ANDROID_API_KEY',
});

expect(result).toEqual(buildGradleExpectedFixture);
Expand Down
13 changes: 13 additions & 0 deletions plugin/src/__tests__/withReactNativeBatchAppDelegate.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { modifyAppDelegate } from '../withReactNativeBatchAppDelegate';
import {
appDelegateExpectedFixture,
appDelegateFixture,
} from '../fixtures/appDelegate';

describe(modifyAppDelegate, () => {
it('should modify the AppDelegate', () => {
const result = modifyAppDelegate(appDelegateFixture);

expect(result).toEqual(appDelegateExpectedFixture);
});
});
319 changes: 319 additions & 0 deletions plugin/src/fixtures/appDelegate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,319 @@
export const appDelegateFixture = `#import "AppDelegate.h"
#if defined(EX_DEV_MENU_ENABLED)
@import EXDevMenu;
#endif
#if defined(EX_DEV_LAUNCHER_ENABLED)
#include <EXDevLauncher/EXDevLauncherController.h>
#import <EXUpdates/EXUpdatesDevLauncherController.h>
#endif
#import <React/RCTBridge.h>
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>
#import <React/RCTLinkingManager.h>
#import <UMCore/UMModuleRegistry.h>
#import <UMReactNativeAdapter/UMNativeModulesProxy.h>
#import <UMReactNativeAdapter/UMModuleRegistryAdapter.h>
#import <EXSplashScreen/EXSplashScreenService.h>
#import <UMCore/UMModuleRegistryProvider.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
@interface AppDelegate () <RCTBridgeDelegate>
@property (nonatomic, strong) UMModuleRegistryAdapter *moduleRegistryAdapter;
@property (nonatomic, strong) NSDictionary *launchOptions;
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
#if defined(FB_SONARKIT_ENABLED) && __has_include(<FlipperKit/FlipperClient.h>)
InitializeFlipper(application);
#endif
self.moduleRegistryAdapter = [[UMModuleRegistryAdapter alloc] initWithModuleRegistryProvider:[[UMModuleRegistryProvider alloc] init]];
self.launchOptions = launchOptions;
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
#ifdef DEBUG
#if defined(EX_DEV_LAUNCHER_ENABLED)
EXDevLauncherController *controller = [EXDevLauncherController sharedInstance];
controller.updatesInterface = [EXUpdatesDevLauncherController sharedInstance];
[controller startWithWindow:self.window delegate:(id<EXDevLauncherControllerDelegate>)self launchOptions:launchOptions];
#else
[self initializeReactNativeApp];
#endif
#else
EXUpdatesAppController *controller = [EXUpdatesAppController sharedInstance];
controller.delegate = self;
[controller startAndShowLaunchScreen:self.window];
#endif
[super application:application didFinishLaunchingWithOptions:launchOptions];
return YES;
}
- (RCTBridge *)initializeReactNativeApp
{
#if defined(EX_DEV_LAUNCHER_ENABLED)
NSDictionary *launchOptions = [EXDevLauncherController.sharedInstance getLaunchOptions];
#else
NSDictionary *launchOptions = self.launchOptions;
#endif
RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@"main" initialProperties:nil];
rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];
UIViewController *rootViewController = [UIViewController new];
rootViewController.view = rootView;
self.window.rootViewController = rootViewController;
[self.window makeKeyAndVisible];
return bridge;
}
- (NSArray<id<RCTBridgeModule>> *)extraModulesForBridge:(RCTBridge *)bridge
{
NSArray<id<RCTBridgeModule>> *extraModules = [_moduleRegistryAdapter extraModulesForBridge:bridge];
// If you'd like to export some custom RCTBridgeModules that are not Expo modules, add them here!
return extraModules;
}
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge {
#ifdef DEBUG
#if defined(EX_DEV_LAUNCHER_ENABLED)
return [[EXDevLauncherController sharedInstance] sourceUrl];
#else
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
#endif
#else
return [[EXUpdatesAppController sharedInstance] launchAssetUrl];
#endif
}
- (void)appController:(EXUpdatesAppController *)appController didStartWithSuccess:(BOOL)success {
appController.bridge = [self initializeReactNativeApp];
EXSplashScreenService *splashScreenService = (EXSplashScreenService *)[UMModuleRegistryProvider getSingletonModuleForClass:[EXSplashScreenService class]];
[splashScreenService showSplashScreenFor:self.window.rootViewController];
}
// Linking API
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
#if defined(EX_DEV_LAUNCHER_ENABLED)
if ([EXDevLauncherController.sharedInstance onDeepLink:url options:options]) {
return true;
}
#endif
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
#if defined(EX_DEV_LAUNCHER_ENABLED)
@implementation AppDelegate (EXDevLauncherControllerDelegate)
- (void)devLauncherController:(EXDevLauncherController *)developmentClientController
didStartWithSuccess:(BOOL)success
{
developmentClientController.appBridge = [self initializeReactNativeApp];
EXSplashScreenService *splashScreenService = (EXSplashScreenService *)[UMModuleRegistryProvider getSingletonModuleForClass:[EXSplashScreenService class]];
[splashScreenService showSplashScreenFor:self.window.rootViewController];
}
@end
#endif
`;

export const appDelegateExpectedFixture = `#import "AppDelegate.h"
#import <RNBatchPush/RNBatch.h>
#if defined(EX_DEV_MENU_ENABLED)
@import EXDevMenu;
#endif
#if defined(EX_DEV_LAUNCHER_ENABLED)
#include <EXDevLauncher/EXDevLauncherController.h>
#import <EXUpdates/EXUpdatesDevLauncherController.h>
#endif
#import <React/RCTBridge.h>
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>
#import <React/RCTLinkingManager.h>
#import <UMCore/UMModuleRegistry.h>
#import <UMReactNativeAdapter/UMNativeModulesProxy.h>
#import <UMReactNativeAdapter/UMModuleRegistryAdapter.h>
#import <EXSplashScreen/EXSplashScreenService.h>
#import <UMCore/UMModuleRegistryProvider.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
@interface AppDelegate () <RCTBridgeDelegate>
@property (nonatomic, strong) UMModuleRegistryAdapter *moduleRegistryAdapter;
@property (nonatomic, strong) NSDictionary *launchOptions;
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[RNBatch start];
[BatchUNUserNotificationCenterDelegate registerAsDelegate];
[BatchUNUserNotificationCenterDelegate sharedInstance].showForegroundNotifications = true;
#if defined(FB_SONARKIT_ENABLED) && __has_include(<FlipperKit/FlipperClient.h>)
InitializeFlipper(application);
#endif
self.moduleRegistryAdapter = [[UMModuleRegistryAdapter alloc] initWithModuleRegistryProvider:[[UMModuleRegistryProvider alloc] init]];
self.launchOptions = launchOptions;
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
#ifdef DEBUG
#if defined(EX_DEV_LAUNCHER_ENABLED)
EXDevLauncherController *controller = [EXDevLauncherController sharedInstance];
controller.updatesInterface = [EXUpdatesDevLauncherController sharedInstance];
[controller startWithWindow:self.window delegate:(id<EXDevLauncherControllerDelegate>)self launchOptions:launchOptions];
#else
[self initializeReactNativeApp];
#endif
#else
EXUpdatesAppController *controller = [EXUpdatesAppController sharedInstance];
controller.delegate = self;
[controller startAndShowLaunchScreen:self.window];
#endif
[super application:application didFinishLaunchingWithOptions:launchOptions];
return YES;
}
- (RCTBridge *)initializeReactNativeApp
{
#if defined(EX_DEV_LAUNCHER_ENABLED)
NSDictionary *launchOptions = [EXDevLauncherController.sharedInstance getLaunchOptions];
#else
NSDictionary *launchOptions = self.launchOptions;
#endif
RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@"main" initialProperties:nil];
rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];
UIViewController *rootViewController = [UIViewController new];
rootViewController.view = rootView;
self.window.rootViewController = rootViewController;
[self.window makeKeyAndVisible];
return bridge;
}
- (NSArray<id<RCTBridgeModule>> *)extraModulesForBridge:(RCTBridge *)bridge
{
NSArray<id<RCTBridgeModule>> *extraModules = [_moduleRegistryAdapter extraModulesForBridge:bridge];
// If you'd like to export some custom RCTBridgeModules that are not Expo modules, add them here!
return extraModules;
}
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge {
#ifdef DEBUG
#if defined(EX_DEV_LAUNCHER_ENABLED)
return [[EXDevLauncherController sharedInstance] sourceUrl];
#else
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
#endif
#else
return [[EXUpdatesAppController sharedInstance] launchAssetUrl];
#endif
}
- (void)appController:(EXUpdatesAppController *)appController didStartWithSuccess:(BOOL)success {
appController.bridge = [self initializeReactNativeApp];
EXSplashScreenService *splashScreenService = (EXSplashScreenService *)[UMModuleRegistryProvider getSingletonModuleForClass:[EXSplashScreenService class]];
[splashScreenService showSplashScreenFor:self.window.rootViewController];
}
// Linking API
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
#if defined(EX_DEV_LAUNCHER_ENABLED)
if ([EXDevLauncherController.sharedInstance onDeepLink:url options:options]) {
return true;
}
#endif
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
#if defined(EX_DEV_LAUNCHER_ENABLED)
@implementation AppDelegate (EXDevLauncherControllerDelegate)
- (void)devLauncherController:(EXDevLauncherController *)developmentClientController
didStartWithSuccess:(BOOL)success
{
developmentClientController.appBridge = [self initializeReactNativeApp];
EXSplashScreenService *splashScreenService = (EXSplashScreenService *)[UMModuleRegistryProvider getSingletonModuleForClass:[EXSplashScreenService class]];
[splashScreenService showSplashScreenFor:self.window.rootViewController];
}
@end
#endif
`;
2 changes: 1 addition & 1 deletion plugin/src/fixtures/buildGradle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ android {
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.0"
resValue "string", "BATCH_API_KEY", "FAKE_API_KEY"
resValue "string", "BATCH_API_KEY", "FAKE_ANDROID_API_KEY"
}
splits {
abi {
Expand Down
8 changes: 6 additions & 2 deletions plugin/src/withReactNativeBatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,24 @@ import {
import { withReactNativeBatchMainActivity } from './withReactNativeBatchMainActivity';
import { withReactNativeBatchAppBuildGradle } from './withReactNativeBatchAppBuildGradle';
import { withReactNativeBatchProjectBuildGradle } from './withReactNativeBatchProjectBuildGradle';
import { withReactNativeBatchInfoPlist } from './withReactNativeBatchInfoPlist';
import { withReactNativeBatchAppDelegate } from './withReactNativeBatchAppDelegate';

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

let newConfig = withGoogleServicesFile(config);
newConfig = withClassPath(newConfig);
newConfig = withApplyPlugin(newConfig);
newConfig = withReactNativeBatchAppBuildGradle(newConfig, _props);
newConfig = withReactNativeBatchMainActivity(newConfig);
newConfig = withReactNativeBatchProjectBuildGradle(newConfig);
newConfig = withReactNativeBatchInfoPlist(newConfig, _props);
newConfig = withReactNativeBatchAppDelegate(newConfig);
// Return the modified config.
return newConfig;
};
Expand Down
2 changes: 1 addition & 1 deletion plugin/src/withReactNativeBatchAppBuildGradle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const pushDependencies = (contents: string, props: Props): string => {
newContents =
start +
defaultConfigContents[0] +
` resValue "string", "BATCH_API_KEY", "${props.apiKey}"` +
` resValue "string", "BATCH_API_KEY", "${props.androidApiKey}"` +
'\n ' +
end;
}
Expand Down
Loading

0 comments on commit 10ec82b

Please sign in to comment.