diff --git a/README.md b/README.md index f1f71e873..d7e421791 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ The key difference with the native bridge is that you get a lot of the metadata You will only have to send in a few parameteres when tracking, e.g: ```javascript const GoogleAnalytics = require('react-native-google-analytics-bridge'); +GoogleAnalytics.setTrackerId('UA-12345-1'); GoogleAnalytics.trackScreenView('Home'); GoogleAnalytics.trackEvent('testcategory', 'testaction'); @@ -17,9 +18,7 @@ GoogleAnalytics.trackEvent('testcategory', 'testaction'); 1. `npm install --save react-native-google-analytics-bridge` 2. `rnpm link react-native-google-analytics-bridge` -With this, [rnpm](https://github.com/rnpm/rnpm) will do most of the heavy lifting for linking, **but** you will still need to do some of the manual steps below. - -These are step 5 and 6 from the iOS installation, and step 4 from the Android installation. Specifically for Android step 4, you'll have to add the tracking id. +With this, [rnpm](https://github.com/rnpm/rnpm) will do most of the heavy lifting for linking, **but** for iOS you will still need to do step 5 from the manual installation guide below. ## Manual installation iOS @@ -32,11 +31,7 @@ These are step 5 and 6 from the iOS installation, and step 4 from the Android in 2. SystemConfiguration.framework 3. libz.tbd 4. libsqlite3.0.tbd -6. Under your project properties ➜ "Info", add a new line with the following: - 1. Key: GAITrackingId - 2. Type: String - 3. Value: UA-12345-1 (in other words, your own tracking id). -7. **Optional step**: If you plan on using the advertising identifier (IDFA), then you need to do two things: +6. **Optional step**: If you plan on using the advertising identifier (IDFA), then you need to do two things: 1. Add AdSupport.framework under "Link Binary With Libraries". (As with the other frameworks in step 5). 2. Go to Xcode ➜ `Libraries` ➜ `RCTGoogleAnalyticsBridge.xcodeproj` ➜ right-click `google-analytics-lib`. Here you need to `Add files to ..`, and add `libAdIdAccess.a` from the `google-analytics-lib` directory. This directory is located in the same `node_modules` path as in step 3. @@ -87,8 +82,8 @@ Consult [this guide](https://developer.android.com/sdk/installing/adding-package .setBundleAssetName("index.android.bundle") .setJSMainModuleName("index.android") .addPackage(new MainReactPackage()) - // Step 2; register package, with your GA tracking id: - .addPackage(new GoogleAnalyticsBridgePackage("UA-12345-1")) + // Step 2; register package: + .addPackage(new GoogleAnalyticsBridgePackage()) .setUseDeveloperSupport(BuildConfig.DEBUG) .setInitialLifecycleState(LifecycleState.RESUMED) .build(); @@ -100,6 +95,16 @@ Consult [this guide](https://developer.android.com/sdk/installing/adding-package ## Javascript API +### setTrackerId(trackerId) +* **trackerId (required):** String, your tracker id, something like: UA-12345-1 + +**Important**: Call **once** on app startup to set the tracker id for all subsequent static calls. + +```javascript +const GoogleAnalytics = require('react-native-google-analytics-bridge'); +GoogleAnalytics.setTrackerId('UA-12345-1') +``` + ### trackScreenView(screenName) * **screenName (required):** String, name of current screen @@ -228,7 +233,9 @@ GoogleAnalytics.setUser('12345678'); * **enabled (required):** Boolean, true to allow IDFA collection, defaults to `true`. -**Important**: For iOS you can only use this method if you have done the optional step 7 from the installation guide. +Also called advertising identifier collection, and is used for advertising features. + +**Important**: For iOS you can only use this method if you have done the optional step 6 from the installation guide. Only enable this (and link the appropriate libraries) if you plan to use advertising features in your app, or else your app may get rejected from the AppStore. See the [Google Analytics](https://developers.google.com/analytics/devguides/collection/ios/v3/campaigns#ios-install) for more info. @@ -305,6 +312,7 @@ In order to control the `logLevel` you can add an item in your `Info.plist` with ## Roadmap +- [ ] Support for A/B testing - [ ] Ecommerce: checkout process - [ ] Ecommerce: impressions - [ ] Campaigns @@ -315,4 +323,4 @@ In order to control the `logLevel` you can add an item in your `Info.plist` with ## peerDependencies This library should work with at least React Native 0.11 and up, but has been tested mostly with 0.17. -I've decided to remove the React Native peerDependency since some users have had issues with how npm handles peerDependencies, especially with -rc versions. +I've decided to remove the React Native peerDependency since some users have had issues with how npm handles peerDependencies, especially with -rc versions. \ No newline at end of file diff --git a/android/src/main/java/com/idehub/GoogleAnalyticsBridge/GoogleAnalyticsBridge.java b/android/src/main/java/com/idehub/GoogleAnalyticsBridge/GoogleAnalyticsBridge.java index ecd65f64c..219917a78 100644 --- a/android/src/main/java/com/idehub/GoogleAnalyticsBridge/GoogleAnalyticsBridge.java +++ b/android/src/main/java/com/idehub/GoogleAnalyticsBridge/GoogleAnalyticsBridge.java @@ -21,7 +21,7 @@ public GoogleAnalyticsBridge(ReactApplicationContext reactContext, String tracki _trackingId = trackingId; } - private String _trackingId; + private final String _trackingId; @Override public String getName() { @@ -48,12 +48,13 @@ synchronized GoogleAnalytics getAnalyticsInstance() { @Override public Map getConstants() { final Map constants = new HashMap<>(); + constants.put("nativeTrackerId", _trackingId); return constants; } @ReactMethod - public void trackScreenView(String screenName){ - Tracker tracker = getTracker(_trackingId); + public void trackScreenView(String trackerId, String screenName){ + Tracker tracker = getTracker(trackerId); if (tracker != null) { @@ -64,8 +65,8 @@ public void trackScreenView(String screenName){ } @ReactMethod - public void trackEvent(String category, String action, ReadableMap optionalValues){ - Tracker tracker = getTracker(_trackingId); + public void trackEvent(String trackerId, String category, String action, ReadableMap optionalValues){ + Tracker tracker = getTracker(trackerId); if (tracker != null) { @@ -87,8 +88,8 @@ public void trackEvent(String category, String action, ReadableMap optionalValue } @ReactMethod - public void trackTiming(String category, Double value, ReadableMap optionalValues){ - Tracker tracker = getTracker(_trackingId); + public void trackTiming(String trackerId, String category, Double value, ReadableMap optionalValues){ + Tracker tracker = getTracker(trackerId); if (tracker != null) { @@ -110,8 +111,8 @@ public void trackTiming(String category, Double value, ReadableMap optionalValue } @ReactMethod - public void trackPurchaseEvent(ReadableMap product, ReadableMap transaction, String eventCategory, String eventAction){ - Tracker tracker = getTracker(_trackingId); + public void trackPurchaseEvent(String trackerId, ReadableMap product, ReadableMap transaction, String eventCategory, String eventAction){ + Tracker tracker = getTracker(trackerId); if (tracker != null) { Product ecommerceProduct = new Product() @@ -143,9 +144,9 @@ public void trackPurchaseEvent(ReadableMap product, ReadableMap transaction, Str } @ReactMethod - public void trackException(String error, Boolean fatal) + public void trackException(String trackerId, String error, Boolean fatal) { - Tracker tracker = getTracker(_trackingId); + Tracker tracker = getTracker(trackerId); if (tracker != null) { tracker.send(new HitBuilders.ExceptionBuilder() @@ -156,9 +157,9 @@ public void trackException(String error, Boolean fatal) } @ReactMethod - public void setUser(String userId) + public void setUser(String trackerId, String userId) { - Tracker tracker = getTracker(_trackingId); + Tracker tracker = getTracker(trackerId); if (tracker != null) { tracker.set("&uid", userId); @@ -166,9 +167,9 @@ public void setUser(String userId) } @ReactMethod - public void allowIDFA(Boolean enabled) + public void allowIDFA(String trackerId, Boolean enabled) { - Tracker tracker = getTracker(_trackingId); + Tracker tracker = getTracker(trackerId); if (tracker != null) { tracker.enableAdvertisingIdCollection(enabled); @@ -176,9 +177,9 @@ public void allowIDFA(Boolean enabled) } @ReactMethod - public void trackSocialInteraction(String network, String action, String targetUrl) + public void trackSocialInteraction(String trackerId, String network, String action, String targetUrl) { - Tracker tracker = getTracker(_trackingId); + Tracker tracker = getTracker(trackerId); if (tracker != null) { tracker.send(new HitBuilders.SocialBuilder() @@ -210,8 +211,8 @@ public void setDispatchInterval(Integer intervalInSeconds){ } @ReactMethod - public void setTrackUncaughtExceptions(Boolean enabled){ - Tracker tracker = getTracker(_trackingId); + public void setTrackUncaughtExceptions(String trackerId, Boolean enabled){ + Tracker tracker = getTracker(trackerId); if (tracker != null) { @@ -221,8 +222,8 @@ public void setTrackUncaughtExceptions(Boolean enabled){ @ReactMethod - public void setAnonymizeIp(Boolean enabled){ - Tracker tracker = getTracker(_trackingId); + public void setAnonymizeIp(String trackerId, Boolean enabled){ + Tracker tracker = getTracker(trackerId); if (tracker != null) { @@ -232,11 +233,11 @@ public void setAnonymizeIp(Boolean enabled){ @ReactMethod public void setOptOut(Boolean enabled){ - GoogleAnalytics analytics = getAnalyticsInstance(); + GoogleAnalytics analytics = getAnalyticsInstance(); - if (analytics != null) - { + if (analytics != null) + { analytics.setAppOptOut(enabled); - } + } } } diff --git a/android/src/main/java/com/idehub/GoogleAnalyticsBridge/GoogleAnalyticsBridgePackage.java b/android/src/main/java/com/idehub/GoogleAnalyticsBridge/GoogleAnalyticsBridgePackage.java index eebec1ed1..017c4a38d 100644 --- a/android/src/main/java/com/idehub/GoogleAnalyticsBridge/GoogleAnalyticsBridgePackage.java +++ b/android/src/main/java/com/idehub/GoogleAnalyticsBridge/GoogleAnalyticsBridgePackage.java @@ -16,6 +16,10 @@ public GoogleAnalyticsBridgePackage(String trackingId) { _trackingId = trackingId; } + public GoogleAnalyticsBridgePackage() { + this(null); + } + private String _trackingId; @Override diff --git a/example/index.android.js b/example/index.android.js index 33574a96a..fc4c25bfd 100644 --- a/example/index.android.js +++ b/example/index.android.js @@ -15,6 +15,8 @@ const GoogleAnalytics = require('react-native-google-analytics-bridge'); var example = React.createClass({ render: function() { + GoogleAnalytics.setTrackerId('UA-12345-2'); + GoogleAnalytics.setDryRun(true); GoogleAnalytics.trackEvent('testcategory', 'Hello Android'); GoogleAnalytics.trackScreenView('Home'); @@ -52,6 +54,12 @@ var example = React.createClass({ GoogleAnalytics.setUser('12345678'); GoogleAnalytics.allowIDFA(true); + + GoogleAnalytics.setDispatchInterval(30); + + GoogleAnalytics.setOptOut(true); + + GoogleAnalytics.setAnonymizeIp(true); return ( diff --git a/example/index.ios.js b/example/index.ios.js index 5d96eaaad..c84f29275 100644 --- a/example/index.ios.js +++ b/example/index.ios.js @@ -16,6 +16,8 @@ const GoogleAnalytics = require('react-native-google-analytics-bridge'); var example = React.createClass({ render: function() { + GoogleAnalytics.setTrackerId('UA-12345-2'); + GoogleAnalytics.setDryRun(true); GoogleAnalytics.trackEvent('testcategory', 'Hello iOS'); GoogleAnalytics.trackScreenView('Home'); @@ -52,6 +54,12 @@ var example = React.createClass({ GoogleAnalytics.setUser('12345678'); GoogleAnalytics.allowIDFA(true); + + GoogleAnalytics.setDispatchInterval(30); + + GoogleAnalytics.setOptOut(true); + + GoogleAnalytics.setAnonymizeIp(true); return ( diff --git a/index.js b/index.js index b8336d5a9..92ac7e998 100644 --- a/index.js +++ b/index.js @@ -2,13 +2,22 @@ const GoogleAnalyticsBridge = require("react-native").NativeModules.GoogleAnalyticsBridge; +let _trackerId = GoogleAnalyticsBridge.nativeTrackerId; + +const getTrackerId = () => { + if (!_trackerId) { + throw new Error("TrackerId not set. See documentation for more details"); + } + return _trackerId +} + class GoogleAnalytics { /** * Track the current screen/view * @param {String} screenName The name of the current screen */ static trackScreenView(screenName) { - GoogleAnalyticsBridge.trackScreenView(screenName); + GoogleAnalyticsBridge.trackScreenView(getTrackerId(), screenName); } /** @@ -18,7 +27,7 @@ class GoogleAnalytics { * @param {Object} optionalValues An object containing optional label and value */ static trackEvent(category, action, optionalValues = {}) { - GoogleAnalyticsBridge.trackEvent(category, action, optionalValues); + GoogleAnalyticsBridge.trackEvent(getTrackerId(), category, action, optionalValues); } /** @@ -28,7 +37,7 @@ class GoogleAnalytics { * @param {Object} optionalValues An object containing optional name and label */ static trackTiming(category, value, optionalValues = {}) { - GoogleAnalyticsBridge.trackTiming(category, value, optionalValues); + GoogleAnalyticsBridge.trackTiming(getTrackerId(), category, value, optionalValues); } /** @@ -39,7 +48,7 @@ class GoogleAnalytics { * @param {String} eventAction The event action, defaults to Purchase */ static trackPurchaseEvent(product = {}, transaction = {}, eventCategory = "Ecommerce", eventAction = "Purchase") { - GoogleAnalyticsBridge.trackPurchaseEvent(product, transaction, eventCategory, eventAction); + GoogleAnalyticsBridge.trackPurchaseEvent(getTrackerId(), product, transaction, eventCategory, eventAction); } /** @@ -48,7 +57,7 @@ class GoogleAnalytics { * @param {Boolean} fatal A value indiciating if the error was fatal, defaults to false */ static trackException(error, fatal = false) { - GoogleAnalyticsBridge.trackException(error, fatal); + GoogleAnalyticsBridge.trackException(getTrackerId(), error, fatal); } /** @@ -56,7 +65,7 @@ class GoogleAnalytics { * @param {String} userId The current userId */ static setUser(userId) { - GoogleAnalyticsBridge.setUser(userId); + GoogleAnalyticsBridge.setUser(getTrackerId(), userId); } /** @@ -64,7 +73,7 @@ class GoogleAnalytics { * @param {Boolean} enabled Defaults to true */ static allowIDFA(enabled = true) { - GoogleAnalyticsBridge.allowIDFA(enabled); + GoogleAnalyticsBridge.allowIDFA(getTrackerId(), enabled); } /** @@ -74,7 +83,7 @@ class GoogleAnalytics { * @param {String} targetUrl */ static trackSocialInteraction(network, action, targetUrl) { - GoogleAnalyticsBridge.trackSocialInteraction(network, action, targetUrl); + GoogleAnalyticsBridge.trackSocialInteraction(getTrackerId(), network, action, targetUrl); } /** @@ -101,16 +110,16 @@ class GoogleAnalytics { * @param {Boolean} enabled */ static setTrackUncaughtExceptions(enabled) { - GoogleAnalyticsBridge.setTrackUncaughtExceptions(enabled); + GoogleAnalyticsBridge.setTrackUncaughtExceptions(getTrackerId(), enabled); } /** * Sets if AnonymizeIp is enabled - * If enabled the last octet of the IP address will be removed + * If enabled the last octet of the IP address will be removed * @param {Boolean} enabled */ static setAnonymizeIp(enabled) { - GoogleAnalyticsBridge.setAnonymizeIp(enabled); + GoogleAnalyticsBridge.setAnonymizeIp(getTrackerId(), enabled); } /** @@ -122,6 +131,14 @@ class GoogleAnalytics { GoogleAnalyticsBridge.setOptOut(enabled); } + /** + * Sets new tracker ID for all subsequent static calls + * @param {String} tracker ID + */ + static setTrackerId(trackerId) { + _trackerId = trackerId; + } + } module.exports = GoogleAnalytics; diff --git a/ios/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge.m b/ios/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge.m index 512f0c714..d84c107bd 100644 --- a/ios/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge.m +++ b/ios/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge.m @@ -12,17 +12,18 @@ @implementation RCTGoogleAnalyticsBridge { } +NSMutableDictionary* trackers; +NSString *staticTrackerId; + - (instancetype)init { if ((self = [super init])) { - // Optional: automatically send uncaught exceptions to Google Analytics. + trackers = [NSMutableDictionary new]; + [GAI sharedInstance].trackUncaughtExceptions = YES; - - // Optional: set Google Analytics dispatch interval to e.g. 20 seconds. [GAI sharedInstance].dispatchInterval = 20; - // Initialize tracker. Replace with your tracking ID. - [[GAI sharedInstance] trackerWithTrackingId:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"GAITrackingId"]]; + staticTrackerId = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"GAITrackingId"]; NSString *logLevel = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"GAILogLevel"]; if (logLevel != nil) { @@ -32,30 +33,45 @@ - (instancetype)init return self; } +- (id)getTracker:(NSString*)trackerId { + if ([trackers objectForKey:trackerId] == nil) { + [trackers + setObject:[[GAI sharedInstance] trackerWithTrackingId:trackerId] + forKey:trackerId]; + } + + return [trackers objectForKey:trackerId]; +} + +- (NSDictionary *)constantsToExport +{ + return @{ @"nativeTrackerId": staticTrackerId }; +} + RCT_EXPORT_MODULE(); -RCT_EXPORT_METHOD(trackScreenView:(NSString *)screenName) +RCT_EXPORT_METHOD(trackScreenView:(NSString *)trackerId screenName:(NSString *)screenName) { - id tracker = [[GAI sharedInstance] defaultTracker]; - [tracker set:kGAIScreenName + id tracker = [self getTracker:trackerId]; + [tracker set:kGAIScreenName value:screenName]; - [tracker send:[[GAIDictionaryBuilder createScreenView] build]]; + [tracker send:[[GAIDictionaryBuilder createScreenView] build]]; } -RCT_EXPORT_METHOD(trackEvent:(NSString *)category action:(NSString *)action optionalValues:(NSDictionary *)optionalValues) +RCT_EXPORT_METHOD(trackEvent:(NSString *)trackerId category:(NSString *)category action:(NSString *)action optionalValues:(NSDictionary *)optionalValues) { - id tracker = [[GAI sharedInstance] defaultTracker]; - NSString *label = [RCTConvert NSString:optionalValues[@"label"]]; - NSNumber *value = [RCTConvert NSNumber:optionalValues[@"value"]]; - [tracker send:[[GAIDictionaryBuilder createEventWithCategory:category + id tracker = [self getTracker:trackerId]; + NSString *label = [RCTConvert NSString:optionalValues[@"label"]]; + NSNumber *value = [RCTConvert NSNumber:optionalValues[@"value"]]; + [tracker send:[[GAIDictionaryBuilder createEventWithCategory:category action:action label:label value:value] build]]; } -RCT_EXPORT_METHOD(trackTiming:(nonnull NSString *)category value:(nonnull NSNumber *)value optionalValues:(nonnull NSDictionary *)optionalValues) +RCT_EXPORT_METHOD(trackTiming:(NSString *)trackerId category:(nonnull NSString *)category value:(nonnull NSNumber *)value optionalValues:(nonnull NSDictionary *)optionalValues) { - id tracker = [[GAI sharedInstance] defaultTracker]; + id tracker = [self getTracker:trackerId]; NSString *name = [RCTConvert NSString:optionalValues[@"name"]]; NSString *label = [RCTConvert NSString:optionalValues[@"label"]]; [tracker send:[[GAIDictionaryBuilder createTimingWithCategory:category @@ -64,9 +80,9 @@ - (instancetype)init label:label] build]]; } -RCT_EXPORT_METHOD(trackPurchaseEvent:(NSDictionary *)product transaction:(NSDictionary *)transaction eventCategory:(NSString *)eventCategory eventAction:(NSString *)eventAction) +RCT_EXPORT_METHOD(trackPurchaseEvent:(NSString *)trackerId product:(NSDictionary *)product transaction:(NSDictionary *)transaction eventCategory:(NSString *)eventCategory eventAction:(NSString *)eventAction) { - id tracker = [[GAI sharedInstance] defaultTracker]; + id tracker = [self getTracker:trackerId]; NSString *productId = [RCTConvert NSString:product[@"id"]]; NSString *productName = [RCTConvert NSString:product[@"name"]]; NSString *productCategory = [RCTConvert NSString:product[@"category"]]; @@ -107,30 +123,30 @@ - (instancetype)init [tracker send:[builder build]]; } -RCT_EXPORT_METHOD(trackException:(NSString *)error fatal:(BOOL)fatal) +RCT_EXPORT_METHOD(trackException:(NSString *)trackerId error:(NSString *)error fatal:(BOOL)fatal) { - id tracker = [[GAI sharedInstance] defaultTracker]; - [tracker send:[[GAIDictionaryBuilder createExceptionWithDescription:error + id tracker = [self getTracker:trackerId]; + [tracker send:[[GAIDictionaryBuilder createExceptionWithDescription:error withFatal:[NSNumber numberWithBool:fatal]] build]]; } -RCT_EXPORT_METHOD(setUser:(NSString *)userId) +RCT_EXPORT_METHOD(setUser:(NSString *)trackerId userId:(NSString *)userId) { - id tracker = [[GAI sharedInstance] defaultTracker]; - [tracker set:kGAIUserId + id tracker = [self getTracker:trackerId]; + [tracker set:kGAIUserId value:userId]; } -RCT_EXPORT_METHOD(allowIDFA:(BOOL)enabled) +RCT_EXPORT_METHOD(allowIDFA:(NSString *)trackerId enabled:(BOOL)enabled) { - id tracker = [[GAI sharedInstance] defaultTracker]; - tracker.allowIDFACollection = enabled; + id tracker = [self getTracker:trackerId]; + tracker.allowIDFACollection = enabled; } -RCT_EXPORT_METHOD(trackSocialInteraction:(NSString *)network action:(NSString *)action targetUrl:(NSString *)targetUrl) +RCT_EXPORT_METHOD(trackSocialInteraction:(NSString *)trackerId network:(NSString *)network action:(NSString *)action targetUrl:(NSString *)targetUrl) { - id tracker = [[GAI sharedInstance] defaultTracker]; - [tracker send:[[GAIDictionaryBuilder createSocialWithNetwork:network + id tracker = [self getTracker:trackerId]; + [tracker send:[[GAIDictionaryBuilder createSocialWithNetwork:network action:action target:targetUrl] build]]; } @@ -150,9 +166,9 @@ - (instancetype)init [GAI sharedInstance].trackUncaughtExceptions = enabled; } -RCT_EXPORT_METHOD(setAnonymizeIp:(BOOL)enabled) +RCT_EXPORT_METHOD(setAnonymizeIp:(NSString *)trackerId enabled:(BOOL)enabled) { - id tracker = [[GAI sharedInstance] defaultTracker]; + id tracker = [self getTracker:trackerId]; [tracker set:kGAIAnonymizeIp value:enabled ? @"1" : @"0"]; } @@ -161,5 +177,4 @@ - (instancetype)init [GAI sharedInstance].optOut = enabled; } - @end