UNPKG

mixpanel-react-native

Version:

Official React Native Tracking Library for Mixpanel Analytics

353 lines (285 loc) โ€ข 9.95 kB
# React Native Integration Patterns ## Overview The mixpanel-react-native library demonstrates sophisticated React Native integration patterns, including native module bridging, autolinking configuration, and cross-platform compatibility strategies. ## ๐Ÿ”— Native Module Bridge Architecture ### Platform Registration #### iOS (CocoaPods Integration) ```ruby # MixpanelReactNative.podspec Pod::Spec.new do |s| s.name = "MixpanelReactNative" s.platform = :ios, "11.0" s.swift_version = '5.0' s.source_files = "ios/*.{swift,h,m}" # Key dependencies s.dependency "React-Core" s.dependency "Mixpanel-swift", '5.0.0' # Official Mixpanel iOS SDK end ``` **Key Insights**: - Minimum iOS 11.0 support - Swift 5.0 for modern language features - Direct dependency on official Mixpanel iOS SDK - Source files include both Swift and Objective-C bridge files #### Android (Gradle Integration) ```gradle // android/build.gradle android { compileSdkVersion 34 minSdkVersion 21 // Android 5.0+ targetSdkVersion 34 namespace "com.mixpanel.reactnative" // AGP 7.0+ namespace } dependencies { implementation 'com.facebook.react:react-native:+' implementation 'com.mixpanel.android:mixpanel-android:8.1.0' // Official Android SDK } ``` **Key Insights**: - Modern Android API levels (21-34) - Namespace declaration for AGP 7.0+ compatibility - Direct integration with official Mixpanel Android SDK - Dynamic React Native version resolution ### Autolinking Configuration ```javascript // react-native.config.js module.exports = { dependencies: { 'mixpanel-react-native': { platforms: { android: { "sourceDir": "./node_modules/mixpanel-react-native/android", "packageImportPath": "import com.mixpanel.reactnative.MixpanelReactNativePackage;", "packageInstance": "new MixpanelReactNativePackage()" }, ios: { project: './node_modules/mixpanel-react-native/ios/MixpanelReactNative.xcodeproj', } } } } }; ``` **Autolinking Strategy**: - Explicit configuration for both platforms - Custom package registration for Android - Xcode project specification for iOS - Supports React Native 0.60+ autolinking ## ๐Ÿ—๏ธ Native Module Implementation Patterns ### iOS Bridge Pattern (Swift + Objective-C) ```swift // ios/MixpanelReactNative.swift @objc(MixpanelReactNative) open class MixpanelReactNative: NSObject { @objc static func requiresMainQueueSetup() -> Bool { return false // Background initialization OK } @objc func initialize(_ token: String, trackAutomaticEvents: Bool, optOutTrackingByDefault: Bool = false, properties: [String: Any], serverURL: String, useGzipCompression: Bool = false, resolver resolve: RCTPromiseResolveBlock, rejecter reject: RCTPromiseRejectBlock) -> Void { // Native implementation using Mixpanel-swift SDK Mixpanel.initialize(token: token, ...) resolve(true) } } ``` **Bridge Patterns**: - `@objc` decoration for React Native exposure - Promise-based async operations - Background-safe initialization - Direct parameter mapping to native SDK ### Android Bridge Pattern (Java) ```java // android/.../MixpanelReactNativeModule.java public class MixpanelReactNativeModule extends ReactContextBaseJavaModule { @ReactMethod public void initialize(String token, boolean trackAutomaticEvents, boolean optOutTrackingDefault, ReadableMap metadata, String serverURL, boolean useGzipCompression, Promise promise) throws JSONException { // Convert React Native types to native JSONObject mixpanelProperties = ReactNativeHelper.reactToJSON(metadata); // Initialize native SDK MixpanelAPI instance = MixpanelAPI.getInstance(this.mReactContext, token, optOutTrackingDefault, mixpanelProperties, null, trackAutomaticEvents); promise.resolve(null); } } ``` **Bridge Patterns**: - `@ReactMethod` annotation for method exposure - Type conversion utilities (`ReactNativeHelper`) - Thread-safe instance management - Consistent Promise-based API ## ๐Ÿ”„ Fallback Strategy Implementation ### Implementation Router Pattern ```javascript // index.js export class Mixpanel { constructor(token, trackAutomaticEvents, useNative = true, storage) { this.token = token; this.trackAutomaticEvents = trackAutomaticEvents; // Try native implementation first if (useNative && MixpanelReactNative) { this.mixpanelImpl = MixpanelReactNative; return; } else if (useNative) { console.warn( "MixpanelReactNative is not available; using JavaScript mode..." ); } // Fallback to JavaScript implementation this.mixpanelImpl = new MixpanelMain(token, trackAutomaticEvents, storage); } } ``` **Fallback Benefits**: - Graceful degradation when native modules unavailable - Expo compatibility (no native modules allowed) - Web support through React Native Web - Development environment flexibility ## ๐Ÿ“ฑ Platform-Specific Optimizations ### iOS Optimizations ```swift // ios/MixpanelReactNative.swift @objc func setFlushOnBackground(_ token: String, flushOnBackground: Bool, resolver resolve: RCTPromiseResolveBlock, rejecter reject: RCTPromiseRejectBlock) -> Void { let instance = MixpanelReactNative.getMixpanelInstance(token) instance?.flushOnBackground = flushOnBackground // iOS-specific feature resolve(nil) } ``` **iOS-Specific Features**: - Background flushing support - App lifecycle integration - Memory management optimization - Native SDK performance benefits ### Android Optimizations ```java // android/.../MixpanelReactNativeModule.java @ReactMethod public void setFlushBatchSize(final String token, Integer flushBatchSize, Promise promise) { MixpanelAPI instance = MixpanelAPI.getInstance(this.mReactContext, token, true); if (instance == null) { promise.reject("Instance Error", "Failed to get Mixpanel instance"); return; } synchronized (instance) { // Thread-safe operations instance.setFlushBatchSize(flushBatchSize); promise.resolve(null); } } ``` **Android-Specific Features**: - Thread synchronization for safety - Context-aware initialization - Lifecycle-aware processing - Memory-efficient batching ## ๐Ÿ› ๏ธ Development & Build Configuration ### Metro Configuration (Standard) ```javascript // Samples/SimpleMixpanel/metro.config.js const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config'); const config = {}; // Use defaults module.exports = mergeConfig(getDefaultConfig(__dirname), config); ``` **Build Strategy**: - Standard React Native Metro bundler - No custom transformations needed - Works with standard RN build pipeline ### Babel Configuration (Standard) ```javascript // Samples/SimpleMixpanel/babel.config.js module.exports = { presets: ['module:@react-native/babel-preset'], }; ``` **Transpilation Strategy**: - Standard React Native Babel preset - No custom plugins required - ES6+ feature support ## ๐Ÿงช Testing Integration ### Jest Configuration for React Native ```javascript // package.json "jest": { "modulePathIgnorePatterns": ["<rootDir>/Samples/"], "testMatch": ["<rootDir>/__tests__/*.test.js"], "setupFiles": ["<rootDir>/__tests__/jest_setup.js"], "verbose": true, "preset": "react-native" } ``` **Testing Strategy**: - React Native Jest preset - Custom setup for mocking - Isolated test environment - Sample apps excluded from tests ### Native Module Mocking ```javascript // __tests__/jest_setup.js jest.doMock("react-native", () => { return Object.setPrototypeOf({ NativeModules: { MixpanelReactNative: { initialize: jest.fn(), track: jest.fn(), // ... complete API mock }, }, }, ReactNative); }); ``` **Mocking Strategy**: - Complete native module API mocking - Consistent with actual native interface - Enables testing without native dependencies ## ๐Ÿš€ Performance Considerations ### Native vs JavaScript Mode | Aspect | Native Mode | JavaScript Mode | |--------|-------------|-----------------| | **Initialization** | Native SDK optimized | Custom implementation | | **Queue Management** | Native (60s flush) | JavaScript (10s flush) | | **Storage** | Platform-optimized | AsyncStorage | | **Network** | Native HTTP stack | Fetch API | | **Memory** | SDK-managed | Manual management | ### Bundle Size Impact ```javascript // The library is designed for minimal bundle impact: // - Core native bridge code only // - JavaScript fallback loaded conditionally // - No heavy dependencies in main bundle ``` ## ๐Ÿ“‹ Best Practices Discovered ### 1. Graceful Degradation Always provide JavaScript fallback for native functionality ### 2. Promise-Based APIs Consistent async patterns across platforms ### 3. Type Safety Comprehensive input validation before native calls ### 4. Error Handling Native errors properly propagated to JavaScript ### 5. Platform Detection Runtime detection of capabilities rather than build-time ### 6. Memory Management Proper cleanup and instance management ### 7. Thread Safety Synchronization for shared resources ### 8. Configuration Flexibility Runtime configuration over build-time constants