Skip to content

Instantly share code, notes, and snippets.

@hgale
Created January 25, 2018 21:11
Show Gist options
  • Save hgale/9df98958f15a7bfe62af9a3537120a9c to your computer and use it in GitHub Desktop.
Save hgale/9df98958f15a7bfe62af9a3537120a9c to your computer and use it in GitHub Desktop.
BrownFieldsample
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool
// Warm up ReactNative bridge and fetch latest screens
RNEventController.applicationDidLaunch()
{
"name": "ExampleReactNative",
"version": "0.0.1",
"private": true,
"scripts": {
"postinstall": "sed -i '' 's#<RCTAnimation/RCTValueAnimatedNode.h>#\"RCTValueAnimatedNode.h\"#g' ./node_modules/react-native/Libraries/NativeAnimation/RCTNativeAnimatedNodesManager.h; sed -i '' 's#<fishhook/fishhook.h>#\"fishhook.h\"#g' ./node_modules/react-native/Libraries/WebSocket/RCTReconnectingWebSocket.m; sed -i '' 's#\"RCTBridgeModule.h\"#<React/RCTBridgeModule.h>#g' ./node_modules/react-native-event-bridge/ios/MSREventBridge.h; sed -i '' 's#\"RCTEventEmitter.h\"#<React/RCTEventEmitter.h>#g' ./node_modules/react-native-event-bridge/ios/MSREventBridge.h",
"start": "node node_modules/react-native/local-cli/cli.js start",
"test": "jest"
},
"dependencies": {
"lodash": "4.2.0",
"lodash.assign": "^4.2.0",
"node-pre-gyp": "^0.6.39",
"react": "16.0.0-beta.5",
"react-native": "0.49.1",
"react-native-code-push": "^5.2.1",
"react-native-event-bridge": "^0.7.0",
"stacktrace-js": "^1.1.0"
},
"devDependencies": {
"babel-jest": "21.2.0",
"babel-preset-react-native": "4.0.0",
"jest": "21.2.1",
"react-test-renderer": "16.0.0-beta.5"
},
"jest": {
"preset": "react-native"
}
}
// Root component on JS side of bridge:
class ReactNative extends React.Component {
constructor(props) {
super(props);
error.init(this);
}
componentDidMount() {
const { screen, sendSupportedScreens } = this.props;
if (sendSupportedScreens) {
this.sendSupportedScreens();
this.getUpdateMetadata();
} else {
// This is the analytics event that we are seeing the discrepency in, i.e 66% drop off versus the native side
logEvent(this, analyticsEvents.ViewScreen, {screen:screen});
}
if (screen === Screens.Query) {
CodePush.sync({ installMode: CodePush.InstallMode.IMMEDIATE });
}
}
render() {
const { screen } = this.props;
let selectedScreen = <Query {...this.props} />
if (screen === Screens.ExpiredScreenOne) {
selectedScreen = <ExpiredScreenOne {...this.props} />
}
if (screen === Screens.ExpiredScreenTwo) {
selectedScreen = <ExpiredScreenTwo {...this.props} />
}
return (selectedScreen);
}
}
}
ReactNative = CodePush(codePushOptions)(ReactNative);
AppRegistry.registerComponent('ExampleReactNative', () => ReactNative);
// Analytics log function
function logEvent(component, event, properties) {
if (!isReactComponent(component)) {
throw new TypeError("'component' parameter is null or invalid.");
}
if (!isString(event)) {
throw new TypeError("'event' parameter must be a string.");
}
if (!isDictionary(properties)) {
throw new TypeError("'properties' parameter must be a dictionary.");
}
let info = {
event: event,
properties: properties
}
EventBridge.emitEvent(component,
eventsInternal.AnalyticsLogEvent,
{info:info});
}
import Foundation
public typealias RNEventDictionary = [AnyHashable: Any]
public typealias RNEventHandler = (_ info: RNEventDictionary?) -> ()
/// Simple event wrapper for performing generic work
public struct RNEvent {
// MARK: - Properties
/// The unique name of this event
public let name: String
/// A closure to execute arbitrary logic when this event is fired
public let handler: RNEventHandler
// MARK: - Convenience Initializers
/// Creates a new event with a unique name and handler for arbitrary logic
///
/// - Parameters:
/// - name: The unique name of this event
/// - handler: A closure to execute arbitrary logic when this event is fired
public init(name: String, handler: @escaping RNEventHandler) {
self.name = name
self.handler = handler
}
/// Creates a new event with a unique name and handler for arbitrary logic
///
/// - Parameters:
/// - name: The unique name of this event
/// - handler: A closure to execute arbitrary logic when this event is fired
public init(named name: RNEventName, handler: @escaping RNEventHandler) {
self.name = name.rawValue
self.handler = handler
}
}
// MARK: - Equatable
extension RNEvent: Equatable {
public static func ==(lhs: RNEvent, rhs: RNEvent) -> Bool {
return lhs.name == rhs.name
}
}
open class RNEventController: UIViewController {
open class func applicationDidLaunch() {
// Initialize the event bridge
_ = RNEventController.eventBridge
}
/// The event bridge which we'll re-use throughout the app to keep React warmed up and snappy
fileprivate static let eventBridge = RCTBridge(bundleURL: RNEventController.jsCodeLocation, moduleProvider: nil, launchOptions: nil)
fileprivate static var jsCodeLocation: URL? {
// Development loads bundle from local node server
let localServerUrl = URL(string: "http://localhost:8081/index.ios.bundle?platform=ios&dev=true")
// Production loads minified bundle
let bundleUrl = CodePush.bundleURL(forResource: "main", withExtension: "jsbundle", subdirectory: nil, bundle: Bundle(for: RNEventController.self))
return self.isInDeveloperMode ? localServerUrl : bundleUrl
}
fileprivate static let isInDeveloperMode = false
}
// MARK: - MSREventBridgeEventReceiver
extension RNEventController: MSREventBridgeEventReceiver {
public func onEvent(withName name: String?, info: [AnyHashable : Any]?) {
guard let event = self.events.first(where: { $0.name == name }) else {
if let name = name { self.handleMissingEvent(named: name) }
return
}
// This casting is important or it'll crash
let infoDictionary = info as? [String: Any]
// All events emitted from JS are wrapped in an outer `info` key
event.handler(infoDictionary?[RNKeys.info] as? [String: Any])
}
}
// The screen where we are seeing the discrepancy in events:
final class RNUpsellController: RNEventController {
init(screen: String, feature: AppFeature? = nil, completion: SuccessCallback? = nil) {
self.feature = feature
self.completion = completion
let status = App.premium.premiumStatus
self.source = status.upsellSource
let properties = RNUpsellController.viewProperties(screen: screen, status: status)
super.init(screen: screen, properties: properties)
self.setupEvents()
self.logViewSeen()
}
// This is the native analytics event that gets fired when this screen gets displayed, works as expected
override func logViewSeen() {
super.logViewSeen()
if let exp = PhotosSwitchboard.experiment(named: .featureUpsell) {
exp.track(event: AMPLITUDE_EVENT_ACCOUNT_STATUS_VIEW)
}
}
/// Creates a bridge for ReactNative to send analytics events to native
// Issue with analytics discrepancy occurs here, in testing and on my device this all appears to work
self.add(event: RNEvent(named: .AnalyticsLogEvent) { info in
guard let info = info,
let event = info[RNKeys.event] as? String
else { return }
let properties = info[RNKeys.properties] as? [String: Any]
RNAnalyticsProvider.log(event: event, properties: properties)
})
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment