From bb61071f393d2f90ec9d68af330e2207efcb26ba Mon Sep 17 00:00:00 2001 From: Grant Nestor Date: Fri, 15 Jan 2016 12:25:18 -0800 Subject: [PATCH 01/17] Added ecommerce tracking --- README.md | 25 ++++++++++++-- index.js | 14 ++++++++ .../RCTGoogleAnalyticsBridge.m | 34 +++++++++++++++++-- 3 files changed, 68 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 77558b2dc..fc69510ce 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,7 @@ At the moment the implementation exposes three methods: This method only takes one parameter, the name of the current screen view. E. g. `GoogleAnalytics.trackScreenView('Home')`. **Important**: Calling this will also set the "current view" for other calls. So events tracked will be tagged as having occured on the current view, `Home` in this example. This means it is important to track navigation, especially if events can fire on different views. + ### trackEvent(category, action, optionalValues = {}) This method takes takes two required parameters, the event `category` and `action`. The `optionalValues` has two possible properties, `label` and `value`. @@ -114,6 +115,26 @@ E. g. `GoogleAnalytics.trackEvent('testcategory', 'testaction');` or `GoogleAnal **Note**: Label is a string, while value must be a number. +### trackPurchase(id, transaction = {}, item = {}) +This method takes takes three required parameters, the transaction/item `id` and the `transaction` and `item` objects. All fields for the `transaction` and `item` objects (below) are required and there is an optional `category` field for `item` and `currencyCode` field for both. See the [Google Analytics docs](https://developers.google.com/analytics/devguides/collection/ios/v3/ecommerce) for more info. + +```javascript +GoogleAnalytics.trackPurchase(Date.now().toString(), { + affiliation: 'App Store', + revenue: 4.99, + tax: 0, + shipping: 0, + currencyCode: 'us' // optional +}, item = { + name: '', + sku: '', + category: 'Widgets' // optional + price: 4.99, + quantity: 1, + currencyCode: 'us' // optional +}); +``` + ### setDryRun(enabled) This method takes a boolean parameter indicating if the `dryRun` flag should be enabled or not. @@ -121,8 +142,8 @@ When enabled, `GoogleAnalytics.setDryRun(true)`, the native library prevents any ## Roadmap -- [X] dryRun flag -- [ ] Simple Ecommerce +- [x] dryRun flag +- [x] Simple Ecommerce - [ ] Make the library more configureable ## peerDependencies diff --git a/index.js b/index.js index f014d2636..f17b236c1 100644 --- a/index.js +++ b/index.js @@ -12,6 +12,20 @@ class GoogleAnalytics { GoogleAnalyticsBridge.trackEvent(category, action, optionalValues); } + static trackPurchase(id, transaction = { + affiliation: 'App Store', + revenue: 0.99, + tax: 0, + shipping: 0 + }, item = { + name: '', + sku: '', + price: 0.99, + quantity: 1 + }) { + GoogleAnalyticsBridge.trackPurchase(id, transaction, item); + } + static setDryRun(enabled) { GoogleAnalyticsBridge.setDryRun(enabled); } diff --git a/ios/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge.m b/ios/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge.m index a58e0892a..ff2651e1c 100644 --- a/ios/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge.m +++ b/ios/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge.m @@ -34,17 +34,45 @@ - (instancetype)init [tracker send:[[GAIDictionaryBuilder createScreenView] build]]; } -RCT_EXPORT_METHOD(trackEvent:(NSString *)category action:(NSString *)action optionalValues:(NSDictionary *) optionalValues) +RCT_EXPORT_METHOD(trackEvent:(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 - action:action - label:label + action:action + label:label value:value] build]]; } +RCT_EXPORT_METHOD(trackPurchase:(NSString *)transactionId transaction:(NSDictionary *) transaction item:(NSDictionary *) item) +{ + id tracker = [[GAI sharedInstance] defaultTracker]; + NSString *affiliation = [RCTConvert NSString:transaction[@"affiliation"]]; + NSNumber *revenue = [RCTConvert NSNumber:transaction[@"revenue"]]; + NSNumber *tax = [RCTConvert NSNumber:transaction[@"tax"]]; + NSNumber *shipping = [RCTConvert NSNumber:transaction[@"shipping"]]; + NSString *currencyCode = [RCTConvert NSString:transaction[@"currencyCode"]]; + NSString *name = [RCTConvert NSString:item[@"name"]]; + NSString *sku = [RCTConvert NSString:item[@"sku"]]; + NSString *category = [RCTConvert NSString:item[@"category"]]; + NSNumber *price = [RCTConvert NSNumber:item[@"price"]]; + NSNumber *quantity = [RCTConvert NSNumber:item[@"quantity"]]; + [tracker send:[[GAIDictionaryBuilder createTransactionWithId:transactionId + affiliation:affiliation + revenue:revenue + tax:tax + shipping:shipping + currencyCode:currencyCode] build]]; + [tracker send:[[GAIDictionaryBuilder createItemWithTransactionId:transactionId + name:name + sku:sku + category:category + price:price + quantity:quantity + currencyCode:currencyCode] build]]; +} + RCT_EXPORT_METHOD(setDryRun:(BOOL)enabled) { [GAI sharedInstance].dryRun = enabled; From 0a387e5b78b2ae85ce708a7cfc9a2251a11cc964 Mon Sep 17 00:00:00 2001 From: Grant Nestor Date: Wed, 20 Jan 2016 16:30:41 -0800 Subject: [PATCH 02/17] Added exception tracking --- README.md | 34 +++++++++++++++++-- index.js | 10 ++++-- .../RCTGoogleAnalyticsBridge.m | 11 ++++-- 3 files changed, 48 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index fc69510ce..72b248387 100644 --- a/README.md +++ b/README.md @@ -115,8 +115,24 @@ E. g. `GoogleAnalytics.trackEvent('testcategory', 'testaction');` or `GoogleAnal **Note**: Label is a string, while value must be a number. -### trackPurchase(id, transaction = {}, item = {}) -This method takes takes three required parameters, the transaction/item `id` and the `transaction` and `item` objects. All fields for the `transaction` and `item` objects (below) are required and there is an optional `category` field for `item` and `currencyCode` field for both. See the [Google Analytics docs](https://developers.google.com/analytics/devguides/collection/ios/v3/ecommerce) for more info. +### trackPurchase(transactionId, transaction, product) + +* **transactionId (required):** String, a unique ID representing the transaction, this ID should not collide with other transaction IDs +* **transaction (required):** Object + * **affiliation (required):** String, an entity with which the transaction should be affiliated (e.g. a particular store) + * **revenue (required):** Number, the total revenue of a transaction, including tax and shipping + * **tax (required):** Number, the total tax for a transaction + * **shipping (required):** Number, the total cost of shipping for a transaction + * **currencyCode:** String, the local currency of a transaction, defaults to the currency of the view (profile) in which the transactions are being viewed +* **product (required):** Object + * **affiliation (required):** String, the name of the product + * **sku (required):** String, the SKU of a product + * **category:** String, a category to which the product belongs + * **price (required):** Number, the price of a product + * **quantity (required):** Number, the quantity of a product + * **currencyCode:** String, the local currency of a transaction, defaults to the currency of the view (profile) in which the transactions are being viewed + +See the [Google Analytics docs](https://developers.google.com/analytics/devguides/collection/ios/v3/ecommerce) for more info ```javascript GoogleAnalytics.trackPurchase(Date.now().toString(), { @@ -135,6 +151,20 @@ GoogleAnalytics.trackPurchase(Date.now().toString(), { }); ``` +### trackException(error, fatal) + +* **error:** String, a description of the exception (up to 100 characters), accepts nil +* **fatal (required):** Boolean, indicates whether the exception was fatal + +See the [Google Analytics docs](https://developers.google.com/analytics/devguides/collection/ios/v3/exceptions) for more info + +```javascript +try { + ... +} catch(error) { + GoogleAnalytics.trackException(error, false); +} +``` ### setDryRun(enabled) This method takes a boolean parameter indicating if the `dryRun` flag should be enabled or not. diff --git a/index.js b/index.js index f17b236c1..0981e6522 100644 --- a/index.js +++ b/index.js @@ -12,18 +12,22 @@ class GoogleAnalytics { GoogleAnalyticsBridge.trackEvent(category, action, optionalValues); } - static trackPurchase(id, transaction = { + static trackPurchase(transactionId, transaction = { affiliation: 'App Store', revenue: 0.99, tax: 0, shipping: 0 - }, item = { + }, product = { name: '', sku: '', price: 0.99, quantity: 1 }) { - GoogleAnalyticsBridge.trackPurchase(id, transaction, item); + GoogleAnalyticsBridge.trackPurchase(transactionId, transaction, product); + } + + static trackException(error, fatal) { + GoogleAnalyticsBridge.trackException(error, fatal); } static setDryRun(enabled) { diff --git a/ios/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge.m b/ios/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge.m index ff2651e1c..2a00f2d91 100644 --- a/ios/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge.m +++ b/ios/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge.m @@ -34,7 +34,7 @@ - (instancetype)init [tracker send:[[GAIDictionaryBuilder createScreenView] build]]; } -RCT_EXPORT_METHOD(trackEvent:(NSString *)category action:(NSString *) action optionalValues:(NSDictionary *) optionalValues) +RCT_EXPORT_METHOD(trackEvent:(NSString *)category action:(NSString *)action optionalValues:(NSDictionary *)optionalValues) { id tracker = [[GAI sharedInstance] defaultTracker]; NSString *label = [RCTConvert NSString:optionalValues[@"label"]]; @@ -45,7 +45,7 @@ - (instancetype)init value:value] build]]; } -RCT_EXPORT_METHOD(trackPurchase:(NSString *)transactionId transaction:(NSDictionary *) transaction item:(NSDictionary *) item) +RCT_EXPORT_METHOD(trackPurchase:(NSString *)transactionId transaction:(NSDictionary *)transaction item:(NSDictionary *)item) { id tracker = [[GAI sharedInstance] defaultTracker]; NSString *affiliation = [RCTConvert NSString:transaction[@"affiliation"]]; @@ -73,6 +73,13 @@ - (instancetype)init currencyCode:currencyCode] build]]; } +RCT_EXPORT_METHOD(trackException:(NSString *)error fatal(BOOL *)fatal) +{ + id tracker = [[GAI sharedInstance] defaultTracker]; + [tracker send:[[GAIDictionaryBuilder createExceptionWithDescription:error + withFatal:fatal] build]]; +} + RCT_EXPORT_METHOD(setDryRun:(BOOL)enabled) { [GAI sharedInstance].dryRun = enabled; From 6f41f8b0d12af24417f7f7ae32ad7d78019e3e56 Mon Sep 17 00:00:00 2001 From: Grant Nestor Date: Wed, 20 Jan 2016 16:31:41 -0800 Subject: [PATCH 03/17] Added user ID tracking --- README.md | 10 ++++++++++ index.js | 4 ++++ .../RCTGoogleAnalyticsBridge.m | 7 +++++++ 3 files changed, 21 insertions(+) diff --git a/README.md b/README.md index 72b248387..f0cdd3197 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,16 @@ try { GoogleAnalytics.trackException(error, false); } ``` + +### setUser(userId) + +* **userId (required):** String, an **anonymous** identifier that complies with Google Analytic's user ID policy + +See the [Google Analytics](https://developers.google.com/analytics/devguides/collection/ios/v3/user-id) for more info + +```javascript +GoogleAnalytics.setUser('12345678'); +``` ### setDryRun(enabled) This method takes a boolean parameter indicating if the `dryRun` flag should be enabled or not. diff --git a/index.js b/index.js index 0981e6522..cacbd02a6 100644 --- a/index.js +++ b/index.js @@ -30,6 +30,10 @@ class GoogleAnalytics { GoogleAnalyticsBridge.trackException(error, fatal); } + static setUser(userId) { + GoogleAnalyticsBridge.setUser(userId); + } + static setDryRun(enabled) { GoogleAnalyticsBridge.setDryRun(enabled); } diff --git a/ios/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge.m b/ios/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge.m index 2a00f2d91..539334b2f 100644 --- a/ios/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge.m +++ b/ios/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge.m @@ -80,6 +80,13 @@ - (instancetype)init withFatal:fatal] build]]; } +RCT_EXPORT_METHOD(setUser:(NSString *)userId) +{ + id tracker = [[GAI sharedInstance] defaultTracker]; + [tracker set:kGAIUserId + value:userId]; +} + RCT_EXPORT_METHOD(setDryRun:(BOOL)enabled) { [GAI sharedInstance].dryRun = enabled; From 6b7ebab16bb62c1de567e949711afbf3b6faa2a8 Mon Sep 17 00:00:00 2001 From: Grant Nestor Date: Wed, 20 Jan 2016 16:32:11 -0800 Subject: [PATCH 04/17] Added social interaction tracking --- README.md | 13 +++++++++++++ index.js | 4 ++++ .../RCTGoogleAnalyticsBridge.m | 8 ++++++++ 3 files changed, 25 insertions(+) diff --git a/README.md b/README.md index f0cdd3197..f06b84442 100644 --- a/README.md +++ b/README.md @@ -175,6 +175,19 @@ See the [Google Analytics](https://developers.google.com/analytics/devguides/col ```javascript GoogleAnalytics.setUser('12345678'); ``` + +### trackSocialInteraction(network, action, targetUrl) + +* **network (required):** String, name of social network (e.g. 'Facebook', 'Twitter', 'Google+') +* **action (required):** String, social action (e.g. 'Like', 'Share', '+1') +* **targetUrl:** String, url of content being shared + +See the [Google Analytics](https://developers.google.com/analytics/devguides/collection/ios/v3/social) docs for more info + +```javascript +GoogleAnalytics.trackSocialInteraction('Twitter', 'Post'); +``` + ### setDryRun(enabled) This method takes a boolean parameter indicating if the `dryRun` flag should be enabled or not. diff --git a/index.js b/index.js index cacbd02a6..a697f6404 100644 --- a/index.js +++ b/index.js @@ -34,6 +34,10 @@ class GoogleAnalytics { GoogleAnalyticsBridge.setUser(userId); } + static trackSocialInteraction(network, action, targetUrl) { + GoogleAnalyticsBridge.trackSocialInteraction(network, action, targetUrl); + } + static setDryRun(enabled) { GoogleAnalyticsBridge.setDryRun(enabled); } diff --git a/ios/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge.m b/ios/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge.m index 539334b2f..63a69b1da 100644 --- a/ios/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge.m +++ b/ios/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge.m @@ -87,6 +87,14 @@ - (instancetype)init value:userId]; } +RCT_EXPORT_METHOD(trackSocialInteraction:(NSString *)network action:(NSString *)action action:(NSString *)targetUrl) +{ + id tracker = [[GAI sharedInstance] defaultTracker]; + [tracker send:[[GAIDictionaryBuilder createSocialWithNetwork:error + action:action + target:targetUrl] build]]; +} + RCT_EXPORT_METHOD(setDryRun:(BOOL)enabled) { [GAI sharedInstance].dryRun = enabled; From 633b107cacfb772979f910730aab6bdcc114c208 Mon Sep 17 00:00:00 2001 From: Grant Nestor Date: Thu, 21 Jan 2016 08:46:23 -0800 Subject: [PATCH 05/17] Added libAdidAccess for campaign/install tracking --- .../project.pbxproj | 4 ++++ .../google-analytics-lib/libAdIdAccess.a | Bin 0 -> 99320 bytes 2 files changed, 4 insertions(+) create mode 100644 ios/RCTGoogleAnalyticsBridge/google-analytics-lib/libAdIdAccess.a diff --git a/ios/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge.xcodeproj/project.pbxproj b/ios/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge.xcodeproj/project.pbxproj index 848981a97..ac65aaae2 100644 --- a/ios/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge.xcodeproj/project.pbxproj +++ b/ios/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 9325E7FD1C512C5300EF100D /* libAdIdAccess.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9325E7FC1C512C5300EF100D /* libAdIdAccess.a */; }; A79185CA1C30694E001236A6 /* RCTGoogleAnalyticsBridge.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = A79185C91C30694E001236A6 /* RCTGoogleAnalyticsBridge.h */; }; A79185CC1C30694E001236A6 /* RCTGoogleAnalyticsBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = A79185CB1C30694E001236A6 /* RCTGoogleAnalyticsBridge.m */; }; A79185DE1C3070E8001236A6 /* libGoogleAnalyticsServices.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A79185DD1C3070E8001236A6 /* libGoogleAnalyticsServices.a */; }; @@ -26,6 +27,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 9325E7FC1C512C5300EF100D /* libAdIdAccess.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libAdIdAccess.a; sourceTree = ""; }; A79185C61C30694E001236A6 /* libRCTGoogleAnalyticsBridge.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTGoogleAnalyticsBridge.a; sourceTree = BUILT_PRODUCTS_DIR; }; A79185C91C30694E001236A6 /* RCTGoogleAnalyticsBridge.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTGoogleAnalyticsBridge.h; sourceTree = ""; }; A79185CB1C30694E001236A6 /* RCTGoogleAnalyticsBridge.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RCTGoogleAnalyticsBridge.m; sourceTree = ""; }; @@ -48,6 +50,7 @@ buildActionMask = 2147483647; files = ( A79185DE1C3070E8001236A6 /* libGoogleAnalyticsServices.a in Frameworks */, + 9325E7FD1C512C5300EF100D /* libAdIdAccess.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -93,6 +96,7 @@ A79185DA1C3070E8001236A6 /* GAILogger.h */, A79185DB1C3070E8001236A6 /* GAITrackedViewController.h */, A79185DC1C3070E8001236A6 /* GAITracker.h */, + 9325E7FC1C512C5300EF100D /* libAdIdAccess.a */, A79185DD1C3070E8001236A6 /* libGoogleAnalyticsServices.a */, ); path = "google-analytics-lib"; diff --git a/ios/RCTGoogleAnalyticsBridge/google-analytics-lib/libAdIdAccess.a b/ios/RCTGoogleAnalyticsBridge/google-analytics-lib/libAdIdAccess.a new file mode 100644 index 0000000000000000000000000000000000000000..9813beaf3557a517cbdd7aa0971a01031bff4dc6 GIT binary patch literal 99320 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 XnUa)}m`>g#HO2ri5)%ZHGur Date: Thu, 21 Jan 2016 08:53:28 -0800 Subject: [PATCH 06/17] Added campaign/install tracking --- README.md | 12 +++++++++++- index.js | 7 +++++-- .../RCTGoogleAnalyticsBridge.m | 12 +++++++++--- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index f06b84442..52ad2171f 100644 --- a/README.md +++ b/README.md @@ -154,7 +154,7 @@ GoogleAnalytics.trackPurchase(Date.now().toString(), { ### trackException(error, fatal) * **error:** String, a description of the exception (up to 100 characters), accepts nil -* **fatal (required):** Boolean, indicates whether the exception was fatal +* **fatal (required):** Boolean, indicates whether the exception was fatal, defaults to false See the [Google Analytics docs](https://developers.google.com/analytics/devguides/collection/ios/v3/exceptions) for more info @@ -176,6 +176,16 @@ See the [Google Analytics](https://developers.google.com/analytics/devguides/col GoogleAnalytics.setUser('12345678'); ``` +### allowIDFA(enabled) + +* **enabled (required):** Boolean, true to allow IDFA collection, defaults to true + +See the [Google Analytics](https://developers.google.com/analytics/devguides/collection/ios/v3/campaigns#ios-install) for more info + +```javascript +GoogleAnalytics.allowIDFA(true); +``` + ### trackSocialInteraction(network, action, targetUrl) * **network (required):** String, name of social network (e.g. 'Facebook', 'Twitter', 'Google+') diff --git a/index.js b/index.js index a697f6404..4eb281416 100644 --- a/index.js +++ b/index.js @@ -26,7 +26,7 @@ class GoogleAnalytics { GoogleAnalyticsBridge.trackPurchase(transactionId, transaction, product); } - static trackException(error, fatal) { + static trackException(error, fatal = false) { GoogleAnalyticsBridge.trackException(error, fatal); } @@ -34,6 +34,10 @@ class GoogleAnalytics { GoogleAnalyticsBridge.setUser(userId); } + static allowIDFA(enabled = true) { + GoogleAnalyticsBridge.allowIDFA(enabled); + } + static trackSocialInteraction(network, action, targetUrl) { GoogleAnalyticsBridge.trackSocialInteraction(network, action, targetUrl); } @@ -43,5 +47,4 @@ class GoogleAnalytics { } } - module.exports = GoogleAnalytics; diff --git a/ios/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge.m b/ios/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge.m index 63a69b1da..a44f141ac 100644 --- a/ios/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge.m +++ b/ios/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge.m @@ -73,7 +73,7 @@ - (instancetype)init currencyCode:currencyCode] build]]; } -RCT_EXPORT_METHOD(trackException:(NSString *)error fatal(BOOL *)fatal) +RCT_EXPORT_METHOD(trackException:(NSString *)error fatal:(BOOL)fatal) { id tracker = [[GAI sharedInstance] defaultTracker]; [tracker send:[[GAIDictionaryBuilder createExceptionWithDescription:error @@ -87,10 +87,16 @@ - (instancetype)init value:userId]; } -RCT_EXPORT_METHOD(trackSocialInteraction:(NSString *)network action:(NSString *)action action:(NSString *)targetUrl) +RCT_EXPORT_METHOD(allowIDFA:(BOOL)enabled) +{ + id tracker = [[GAI sharedInstance] defaultTracker]; + tracker.allowIDFACollection = enabled; +} + +RCT_EXPORT_METHOD(trackSocialInteraction:(NSString *)network action:(NSString *)action targetUrl:(NSString *)targetUrl) { id tracker = [[GAI sharedInstance] defaultTracker]; - [tracker send:[[GAIDictionaryBuilder createSocialWithNetwork:error + [tracker send:[[GAIDictionaryBuilder createSocialWithNetwork:network action:action target:targetUrl] build]]; } From 02b8c6c6e67117f91d996a4856c8d62cfd5b3e19 Mon Sep 17 00:00:00 2001 From: Grant Nestor Date: Thu, 21 Jan 2016 10:27:20 -0800 Subject: [PATCH 07/17] Added enhanced ecommerce tracking --- index.js | 16 +++---- .../RCTGoogleAnalyticsBridge.m | 45 ++++++++++++++++++- 2 files changed, 49 insertions(+), 12 deletions(-) diff --git a/index.js b/index.js index 4eb281416..f27415107 100644 --- a/index.js +++ b/index.js @@ -12,20 +12,14 @@ class GoogleAnalytics { GoogleAnalyticsBridge.trackEvent(category, action, optionalValues); } - static trackPurchase(transactionId, transaction = { - affiliation: 'App Store', - revenue: 0.99, - tax: 0, - shipping: 0 - }, product = { - name: '', - sku: '', - price: 0.99, - quantity: 1 - }) { + static trackPurchase(transactionId, transaction = {}, product = {}) { GoogleAnalyticsBridge.trackPurchase(transactionId, transaction, product); } + static trackPurchaseEnhanced(product = {}, transaction = {}, eventCategory = "Ecommerce", eventAction = "Purchase") { + GoogleAnalyticsBridge.trackPurchaseEvent(product, transaction, eventCategory, eventAction); + } + static trackException(error, fatal = false) { GoogleAnalyticsBridge.trackException(error, fatal); } diff --git a/ios/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge.m b/ios/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge.m index a44f141ac..7bc9e716a 100644 --- a/ios/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge.m +++ b/ios/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge.m @@ -45,7 +45,7 @@ - (instancetype)init value:value] build]]; } -RCT_EXPORT_METHOD(trackPurchase:(NSString *)transactionId transaction:(NSDictionary *)transaction item:(NSDictionary *)item) +RCT_EXPORT_METHOD(trackPurchaseEnhanced:(NSString *)transactionId transaction:(NSDictionary *)transaction item:(NSDictionary *)item) { id tracker = [[GAI sharedInstance] defaultTracker]; NSString *affiliation = [RCTConvert NSString:transaction[@"affiliation"]]; @@ -73,6 +73,49 @@ - (instancetype)init currencyCode:currencyCode] build]]; } +RCT_EXPORT_METHOD(trackPurchaseEvent:(NSDictionary *)product transaction:(NSDictionary *)transaction eventCategory:(NSString *)eventCategory eventAction:(NSString *)eventAction) +{ + id tracker = [[GAI sharedInstance] defaultTracker]; + NSString *productId = [RCTConvert NSString:product[@"id"]]; + NSString *productName = [RCTConvert NSString:product[@"name"]]; + NSString *productCategory = [RCTConvert NSString:product[@"category"]]; + NSString *productBrand = [RCTConvert NSString:product[@"brand"]]; + NSString *productVariant = [RCTConvert NSString:product[@"variant"]]; + NSNumber *productPrice = [RCTConvert NSNumber:product[@"price"]]; + NSString *productCouponCode = [RCTConvert NSString:product[@"couponCode"]]; + NSNumber *productQuantity = [RCTConvert NSNumber:product[@"quantity"]]; + NSString *transactionId = [RCTConvert NSString:transaction[@"id"]]; + NSString *transactionAffiliation = [RCTConvert NSString:transaction[@"affiliation"]]; + NSNumber *transactionRevenue = [RCTConvert NSNumber:transaction[@"revenue"]]; + NSNumber *transactionTax = [RCTConvert NSNumber:transaction[@"tax"]]; + NSNumber *transactionShipping = [RCTConvert NSNumber:transaction[@"shipping"]]; + NSString *transactionCouponCode = [RCTConvert NSString:transaction[@"couponCode"]]; + GAIEcommerceProduct *product = [[GAIEcommerceProduct alloc] init]; + [product setId:productId]; + [product setName:productName]; + [product setCategory:productCategory]; + [product setBrand:productBrand]; + [product setVariant:productVariant]; + [product setPrice:productPrice]; + [product setCouponCode:productCouponCode]; + [product setQuantity:productQuantity]; + GAIDictionaryBuilder *builder = [GAIDictionaryBuilder createEventWithCategory:eventCategory + action:eventAction + label:nil + value:nil]; + GAIEcommerceProductAction *action = [[GAIEcommerceProductAction alloc] init]; + [action setAction:kGAIPAPurchase]; + [action setTransactionId:transactionId]; + [action setAffiliation:@transactionAffiliation]; + [action setRevenue:transactionRevenue]; + [action setTax:transactionTax]; + [action setShipping:transactionShipping]; + [action setCouponCode:transactionCouponCode]; + [builder setProductAction:action]; + [builder addProduct:product]; + [tracker send:[builder build]]; +} + RCT_EXPORT_METHOD(trackException:(NSString *)error fatal:(BOOL)fatal) { id tracker = [[GAI sharedInstance] defaultTracker]; From c532d3178c8547a6fbb02937162b6b246390c0ed Mon Sep 17 00:00:00 2001 From: Grant Nestor Date: Thu, 21 Jan 2016 10:27:30 -0800 Subject: [PATCH 08/17] Cleaned up README --- README.md | 94 ++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 76 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 52ad2171f..24f92adc1 100644 --- a/README.md +++ b/README.md @@ -101,19 +101,34 @@ Consult [this guide](https://developer.android.com/sdk/installing/adding-package ## Javascript API At the moment the implementation exposes three methods: + ### trackScreenView(screenName) -This method only takes one parameter, the name of the current screen view. E. g. `GoogleAnalytics.trackScreenView('Home')`. + +* **screenName (required):** String, name of current screen **Important**: Calling this will also set the "current view" for other calls. So events tracked will be tagged as having occured on the current view, `Home` in this example. This means it is important to track navigation, especially if events can fire on different views. -### trackEvent(category, action, optionalValues = {}) -This method takes takes two required parameters, the event `category` and `action`. The `optionalValues` has two possible properties, `label` and `value`. +See the [Google Analytics docs](https://developers.google.com/analytics/devguides/collection/ios/v3/screens) for more info + +```javascript +GoogleAnalytics.trackScreenView('Home') +``` -As the name implies, `optionalValues` can be left out, or can contain one or both properties. Whatever floats your boat. +### trackEvent(category, action, optionalValues) -E. g. `GoogleAnalytics.trackEvent('testcategory', 'testaction');` or `GoogleAnalytics.trackEvent('testcategory', 'testaction', { label: "v1.0.3", value: 22 });` +* **category (required):** String, category of event +* **action (required):** String, name of action +* **optionalValues:** Object + * **label:** String + * **value:** Number -**Note**: Label is a string, while value must be a number. +See the [Google Analytics docs](https://developers.google.com/analytics/devguides/collection/ios/v3/events) for more info + +```javascript +GoogleAnalytics.trackEvent('testcategory', 'testaction'); +// or +GoogleAnalytics.trackEvent('testcategory', 'testaction', {label: 'v1.0.3', value: 22}); +``` ### trackPurchase(transactionId, transaction, product) @@ -151,6 +166,49 @@ GoogleAnalytics.trackPurchase(Date.now().toString(), { }); ``` +### trackPurchaseEnhanced(product, transaction, eventCategory, eventAction) + +* **product (required):** Object + * **id:** String + * **name:** String + * **category:** String + * **brand:** String + * **variant:** String + * **price:** Number + * **quantity:** Number + * **couponCode:** String +* **transaction (required):** Object + * **id:** String + * **affiliation:** String, an entity with which the transaction should be affiliated (e.g. a particular store) + * **revenue:** Number + * **tax:** Number + * **shipping:** Number + * **couponCode:** String +* **eventCategory (required):** String, defaults to "Ecommerce" +* **eventAction (required):** String, defaults to "Purchase" + +See the [Google Analytics docs](https://developers.google.com/analytics/devguides/collection/ios/v3/enhanced-ecommerce#measuring-transactions) for more info + +```javascript +GoogleAnalytics.trackPurchase({ + id: 'P12345' + name: 'Android Warhol T-Shirt', + category: 'Apparel/T-Shirts', + brand: 'Google', + variant: 'Black', + price: 29.20, + quantity: 1, + couponCode: 'APPARELSALE' +}, { + id: 'T12345', + affiliation: 'Google Store - Online', + revenue: 37.39, + tax: 2.85, + shipping: 5.34, + couponCode: 'SUMMER2013' +}, 'Ecommerce', 'Purchase'); +``` + ### trackException(error, fatal) * **error:** String, a description of the exception (up to 100 characters), accepts nil @@ -166,6 +224,18 @@ try { } ``` +### trackSocialInteraction(network, action, targetUrl) + +* **network (required):** String, name of social network (e.g. 'Facebook', 'Twitter', 'Google+') +* **action (required):** String, social action (e.g. 'Like', 'Share', '+1') +* **targetUrl:** String, url of content being shared + +See the [Google Analytics](https://developers.google.com/analytics/devguides/collection/ios/v3/social) docs for more info + +```javascript +GoogleAnalytics.trackSocialInteraction('Twitter', 'Post'); +``` + ### setUser(userId) * **userId (required):** String, an **anonymous** identifier that complies with Google Analytic's user ID policy @@ -186,18 +256,6 @@ See the [Google Analytics](https://developers.google.com/analytics/devguides/col GoogleAnalytics.allowIDFA(true); ``` -### trackSocialInteraction(network, action, targetUrl) - -* **network (required):** String, name of social network (e.g. 'Facebook', 'Twitter', 'Google+') -* **action (required):** String, social action (e.g. 'Like', 'Share', '+1') -* **targetUrl:** String, url of content being shared - -See the [Google Analytics](https://developers.google.com/analytics/devguides/collection/ios/v3/social) docs for more info - -```javascript -GoogleAnalytics.trackSocialInteraction('Twitter', 'Post'); -``` - ### setDryRun(enabled) This method takes a boolean parameter indicating if the `dryRun` flag should be enabled or not. From a12fcb7a048c175c84c8fdc6f87357cc53d45f01 Mon Sep 17 00:00:00 2001 From: Christian Brevik Date: Tue, 19 Jan 2016 20:58:59 +0100 Subject: [PATCH 09/17] purchase tracking java impl --- .../com/idehub/GoogleAnalyticsBridge.java | 53 +++++++++++++++---- index.js | 1 - 2 files changed, 43 insertions(+), 11 deletions(-) diff --git a/android/src/main/java/com/idehub/GoogleAnalyticsBridge.java b/android/src/main/java/com/idehub/GoogleAnalyticsBridge.java index 401882e8a..38586007d 100644 --- a/android/src/main/java/com/idehub/GoogleAnalyticsBridge.java +++ b/android/src/main/java/com/idehub/GoogleAnalyticsBridge.java @@ -72,20 +72,53 @@ public void trackEvent(String category, String action, ReadableMap optionalValue if (tracker != null) { - HitBuilders.EventBuilder hit = new HitBuilders.EventBuilder() + HitBuilders.EventBuilder hit = new HitBuilders.EventBuilder() .setCategory(category) .setAction(action); - if (optionalValues.hasKey("label")) - { - hit.setLabel(optionalValues.getString("label")); - } - if (optionalValues.hasKey("value")) - { - hit.setValue(optionalValues.getInt("value")); - } + if (optionalValues.hasKey("label")) + { + hit.setLabel(optionalValues.getString("label")); + } + if (optionalValues.hasKey("value")) + { + hit.setValue(optionalValues.getInt("value")); + } - tracker.send(hit.build()); + tracker.send(hit.build()); + } + } + + @ReactMethod + public void trackPurchaseEvent(ReadableMap product, ReadableMap transaction, String eventCategory, String eventAction){ + Tracker tracker = getTracker(_trackingId); + + if (tracker != null) { + Product product = new Product() + .setId(product.getString("productId")) + .setName(product.getString("name")) + .setCategory(product.getString("category")) + .setBrand(product.getString("brand")) + .setVariant(product.getString("variant")) + .setPrice(product.getDouble("price")) + .setCouponCode(product.getString("couponCode")) + .setQuantity(product.getInt("quantity")); + + ProductAction productAction = new ProductAction(ProductAction.ACTION_PURCHASE) + .setTransactionId(transaction.getString("transactionId")) + .setTransactionAffiliation(transaction.getString("affiliation")) + .setTransactionRevenue(transaction.getDouble("revenue")) + .setTransactionTax(transaction.getDouble("tax")) + .setTransactionShipping(transaction.getDouble("shipping")) + .setTransactionCouponCode(transaction.getString("couponCode")); + + HitBuilders.EventBuilder hit = new HitBuilders.EventBuilder() + .setProduct(product) + .setCategory(category) + .setProductAction(productAction) + .setAction(action); + + t.send(hit.build()); } } diff --git a/index.js b/index.js index f27415107..81f1a06e4 100644 --- a/index.js +++ b/index.js @@ -7,7 +7,6 @@ class GoogleAnalytics { GoogleAnalyticsBridge.trackScreenView(screenName); } - // The optional values are label, and value. Label is a string, value is a number. Eg. { label "v1.0.3", value: 20 } static trackEvent(category, action, optionalValues = {}) { GoogleAnalyticsBridge.trackEvent(category, action, optionalValues); } From 09283a5b5a156f701ff003a138ae54d184856f9b Mon Sep 17 00:00:00 2001 From: Christian Brevik Date: Mon, 25 Jan 2016 17:08:26 +0100 Subject: [PATCH 10/17] fix a couple of erros, remove old purchase-tracking method --- index.js | 8 +-- .../RCTGoogleAnalyticsBridge.m | 55 +++++-------------- 2 files changed, 19 insertions(+), 44 deletions(-) diff --git a/index.js b/index.js index 81f1a06e4..bac6dc973 100644 --- a/index.js +++ b/index.js @@ -11,11 +11,11 @@ class GoogleAnalytics { GoogleAnalyticsBridge.trackEvent(category, action, optionalValues); } - static trackPurchase(transactionId, transaction = {}, product = {}) { - GoogleAnalyticsBridge.trackPurchase(transactionId, transaction, product); - } + // static trackPurchase(transactionId, transaction = {}, product = {}) { + // GoogleAnalyticsBridge.trackPurchase(transactionId, transaction, product); + // } - static trackPurchaseEnhanced(product = {}, transaction = {}, eventCategory = "Ecommerce", eventAction = "Purchase") { + static trackPurchaseEvent(product = {}, transaction = {}, eventCategory = "Ecommerce", eventAction = "Purchase") { GoogleAnalyticsBridge.trackPurchaseEvent(product, transaction, eventCategory, eventAction); } diff --git a/ios/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge.m b/ios/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge.m index 7bc9e716a..d94642878 100644 --- a/ios/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge.m +++ b/ios/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge.m @@ -4,6 +4,9 @@ #import "GAI.h" #import "GAIFields.h" #import "GAIDictionaryBuilder.h" +#import "GAIEcommerceProduct.h" +#import "GAIEcommerceProductAction.h" +#import "GAIEcommerceFields.h" @implementation RCTGoogleAnalyticsBridge { @@ -45,34 +48,6 @@ - (instancetype)init value:value] build]]; } -RCT_EXPORT_METHOD(trackPurchaseEnhanced:(NSString *)transactionId transaction:(NSDictionary *)transaction item:(NSDictionary *)item) -{ - id tracker = [[GAI sharedInstance] defaultTracker]; - NSString *affiliation = [RCTConvert NSString:transaction[@"affiliation"]]; - NSNumber *revenue = [RCTConvert NSNumber:transaction[@"revenue"]]; - NSNumber *tax = [RCTConvert NSNumber:transaction[@"tax"]]; - NSNumber *shipping = [RCTConvert NSNumber:transaction[@"shipping"]]; - NSString *currencyCode = [RCTConvert NSString:transaction[@"currencyCode"]]; - NSString *name = [RCTConvert NSString:item[@"name"]]; - NSString *sku = [RCTConvert NSString:item[@"sku"]]; - NSString *category = [RCTConvert NSString:item[@"category"]]; - NSNumber *price = [RCTConvert NSNumber:item[@"price"]]; - NSNumber *quantity = [RCTConvert NSNumber:item[@"quantity"]]; - [tracker send:[[GAIDictionaryBuilder createTransactionWithId:transactionId - affiliation:affiliation - revenue:revenue - tax:tax - shipping:shipping - currencyCode:currencyCode] build]]; - [tracker send:[[GAIDictionaryBuilder createItemWithTransactionId:transactionId - name:name - sku:sku - category:category - price:price - quantity:quantity - currencyCode:currencyCode] build]]; -} - RCT_EXPORT_METHOD(trackPurchaseEvent:(NSDictionary *)product transaction:(NSDictionary *)transaction eventCategory:(NSString *)eventCategory eventAction:(NSString *)eventAction) { id tracker = [[GAI sharedInstance] defaultTracker]; @@ -90,15 +65,15 @@ - (instancetype)init NSNumber *transactionTax = [RCTConvert NSNumber:transaction[@"tax"]]; NSNumber *transactionShipping = [RCTConvert NSNumber:transaction[@"shipping"]]; NSString *transactionCouponCode = [RCTConvert NSString:transaction[@"couponCode"]]; - GAIEcommerceProduct *product = [[GAIEcommerceProduct alloc] init]; - [product setId:productId]; - [product setName:productName]; - [product setCategory:productCategory]; - [product setBrand:productBrand]; - [product setVariant:productVariant]; - [product setPrice:productPrice]; - [product setCouponCode:productCouponCode]; - [product setQuantity:productQuantity]; + GAIEcommerceProduct *ecommerceProduct = [[GAIEcommerceProduct alloc] init]; + [ecommerceProduct setId:productId]; + [ecommerceProduct setName:productName]; + [ecommerceProduct setCategory:productCategory]; + [ecommerceProduct setBrand:productBrand]; + [ecommerceProduct setVariant:productVariant]; + [ecommerceProduct setPrice:productPrice]; + [ecommerceProduct setCouponCode:productCouponCode]; + [ecommerceProduct setQuantity:productQuantity]; GAIDictionaryBuilder *builder = [GAIDictionaryBuilder createEventWithCategory:eventCategory action:eventAction label:nil @@ -106,13 +81,13 @@ - (instancetype)init GAIEcommerceProductAction *action = [[GAIEcommerceProductAction alloc] init]; [action setAction:kGAIPAPurchase]; [action setTransactionId:transactionId]; - [action setAffiliation:@transactionAffiliation]; + [action setAffiliation:transactionAffiliation]; [action setRevenue:transactionRevenue]; [action setTax:transactionTax]; [action setShipping:transactionShipping]; [action setCouponCode:transactionCouponCode]; [builder setProductAction:action]; - [builder addProduct:product]; + [builder addProduct:ecommerceProduct]; [tracker send:[builder build]]; } @@ -120,7 +95,7 @@ - (instancetype)init { id tracker = [[GAI sharedInstance] defaultTracker]; [tracker send:[[GAIDictionaryBuilder createExceptionWithDescription:error - withFatal:fatal] build]]; + withFatal:[NSNumber numberWithBool:fatal]] build]]; } RCT_EXPORT_METHOD(setUser:(NSString *)userId) From a4a35d5870f1d6d347220b5fd2651a6c7c2a8695 Mon Sep 17 00:00:00 2001 From: Christian Brevik Date: Mon, 25 Jan 2016 17:22:19 +0100 Subject: [PATCH 11/17] readme update --- README.md | 51 ++++++++------------------------------------------- 1 file changed, 8 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 24f92adc1..0bfc8238f 100644 --- a/README.md +++ b/README.md @@ -31,12 +31,13 @@ These are step 5 and 6 from the iOS installation, and step 4 from the Android in 3. Go to `node_modules` ➜ `react-native-google-analytics-bridge` ➜ `ios` ➜ `RCTGoogleAnalyticsBridge` and add the `RCTGoogleAnalyticsBridge.xcodeproj` file. 4. Add libRCTGoogleAnalyticsBridge.a from the linked project to your project properties ➜ "Build Phases" ➜ "Link Binary With Libraries" 5. Next you will have to link a few more SDK framework/libraries which are required by GA (if you do not already have them linked.) Under the same "Link Binary With Libraries", click the + and add the following: - 1. AdSupport.framework - 2. CoreData.framework - 3. SystemConfiguration.framework - 4. libz.tbd - 5. libsqlite3.0.tbd -6. Under your project properties ➜ "Info", add a new line with the following: + 1. CoreData.framework + 2. SystemConfiguration.framework + 3. libz.tbd + 4. libsqlite3.0.tbd + + **Optional**: If you plan on using the advertising identifier (IDFA), then you also need to add AdSupport.framework +7. 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). @@ -130,43 +131,7 @@ GoogleAnalytics.trackEvent('testcategory', 'testaction'); GoogleAnalytics.trackEvent('testcategory', 'testaction', {label: 'v1.0.3', value: 22}); ``` -### trackPurchase(transactionId, transaction, product) - -* **transactionId (required):** String, a unique ID representing the transaction, this ID should not collide with other transaction IDs -* **transaction (required):** Object - * **affiliation (required):** String, an entity with which the transaction should be affiliated (e.g. a particular store) - * **revenue (required):** Number, the total revenue of a transaction, including tax and shipping - * **tax (required):** Number, the total tax for a transaction - * **shipping (required):** Number, the total cost of shipping for a transaction - * **currencyCode:** String, the local currency of a transaction, defaults to the currency of the view (profile) in which the transactions are being viewed -* **product (required):** Object - * **affiliation (required):** String, the name of the product - * **sku (required):** String, the SKU of a product - * **category:** String, a category to which the product belongs - * **price (required):** Number, the price of a product - * **quantity (required):** Number, the quantity of a product - * **currencyCode:** String, the local currency of a transaction, defaults to the currency of the view (profile) in which the transactions are being viewed - -See the [Google Analytics docs](https://developers.google.com/analytics/devguides/collection/ios/v3/ecommerce) for more info - -```javascript -GoogleAnalytics.trackPurchase(Date.now().toString(), { - affiliation: 'App Store', - revenue: 4.99, - tax: 0, - shipping: 0, - currencyCode: 'us' // optional -}, item = { - name: '', - sku: '', - category: 'Widgets' // optional - price: 4.99, - quantity: 1, - currencyCode: 'us' // optional -}); -``` - -### trackPurchaseEnhanced(product, transaction, eventCategory, eventAction) +### trackPurchaseEvent(product, transaction, eventCategory, eventAction) * **product (required):** Object * **id:** String From c4c7b42b6780b97082443a6020ee95f1c5b45d26 Mon Sep 17 00:00:00 2001 From: Christian Brevik Date: Mon, 25 Jan 2016 19:32:38 +0100 Subject: [PATCH 12/17] moved code to correct package, fixed up purchase tracking for java --- .../GoogleAnalyticsBridge.java | 21 ++++++++----------- .../GoogleAnalyticsBridgePackage.java | 0 2 files changed, 9 insertions(+), 12 deletions(-) rename android/src/main/java/com/idehub/{ => GoogleAnalyticsBridge}/GoogleAnalyticsBridge.java (89%) rename android/src/main/java/com/idehub/{ => GoogleAnalyticsBridge}/GoogleAnalyticsBridgePackage.java (100%) diff --git a/android/src/main/java/com/idehub/GoogleAnalyticsBridge.java b/android/src/main/java/com/idehub/GoogleAnalyticsBridge/GoogleAnalyticsBridge.java similarity index 89% rename from android/src/main/java/com/idehub/GoogleAnalyticsBridge.java rename to android/src/main/java/com/idehub/GoogleAnalyticsBridge/GoogleAnalyticsBridge.java index 38586007d..4624d4c7b 100644 --- a/android/src/main/java/com/idehub/GoogleAnalyticsBridge.java +++ b/android/src/main/java/com/idehub/GoogleAnalyticsBridge/GoogleAnalyticsBridge.java @@ -1,9 +1,5 @@ package com.idehub.GoogleAnalyticsBridge; -import android.content.Context; -import android.app.Application; - -import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; @@ -11,8 +7,9 @@ import com.google.android.gms.analytics.GoogleAnalytics; import com.google.android.gms.analytics.HitBuilders; -import com.google.android.gms.analytics.Logger; import com.google.android.gms.analytics.Tracker; +import com.google.android.gms.analytics.ecommerce.Product; +import com.google.android.gms.analytics.ecommerce.ProductAction; import java.util.HashMap; import java.util.Map; @@ -60,9 +57,9 @@ public void trackScreenView(String screenName){ if (tracker != null) { - tracker.setScreenName(screenName); + tracker.setScreenName(screenName); - tracker.send(new HitBuilders.ScreenViewBuilder().build()); + tracker.send(new HitBuilders.ScreenViewBuilder().build()); } } @@ -94,7 +91,7 @@ public void trackPurchaseEvent(ReadableMap product, ReadableMap transaction, Str Tracker tracker = getTracker(_trackingId); if (tracker != null) { - Product product = new Product() + Product ecommerceProduct = new Product() .setId(product.getString("productId")) .setName(product.getString("name")) .setCategory(product.getString("category")) @@ -113,12 +110,12 @@ public void trackPurchaseEvent(ReadableMap product, ReadableMap transaction, Str .setTransactionCouponCode(transaction.getString("couponCode")); HitBuilders.EventBuilder hit = new HitBuilders.EventBuilder() - .setProduct(product) - .setCategory(category) + .addProduct(ecommerceProduct) .setProductAction(productAction) - .setAction(action); + .setCategory(eventCategory) + .setAction(eventAction); - t.send(hit.build()); + tracker.send(hit.build()); } } diff --git a/android/src/main/java/com/idehub/GoogleAnalyticsBridgePackage.java b/android/src/main/java/com/idehub/GoogleAnalyticsBridge/GoogleAnalyticsBridgePackage.java similarity index 100% rename from android/src/main/java/com/idehub/GoogleAnalyticsBridgePackage.java rename to android/src/main/java/com/idehub/GoogleAnalyticsBridge/GoogleAnalyticsBridgePackage.java From a75bcc0edff66b1f76ac3abd0d897874e6f32544 Mon Sep 17 00:00:00 2001 From: Christian Brevik Date: Mon, 25 Jan 2016 19:43:45 +0100 Subject: [PATCH 13/17] java impl for exception, setuser, idfa and social tracking --- .../GoogleAnalyticsBridge.java | 49 ++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/android/src/main/java/com/idehub/GoogleAnalyticsBridge/GoogleAnalyticsBridge.java b/android/src/main/java/com/idehub/GoogleAnalyticsBridge/GoogleAnalyticsBridge.java index 4624d4c7b..cca67e91a 100644 --- a/android/src/main/java/com/idehub/GoogleAnalyticsBridge/GoogleAnalyticsBridge.java +++ b/android/src/main/java/com/idehub/GoogleAnalyticsBridge/GoogleAnalyticsBridge.java @@ -119,13 +119,60 @@ public void trackPurchaseEvent(ReadableMap product, ReadableMap transaction, Str } } + @ReactMethod + public void trackException(String error, Boolean fatal) + { + Tracker tracker = getTracker(_trackingId); + + if (tracker != null) { + tracker.send(new HitBuilders.ExceptionBuilder() + .setDescription(error) + .setFatal(fatal) + .build()); + } + } + + @ReactMethod + public void setUser(String userId) + { + Tracker tracker = getTracker(_trackingId); + + if (tracker != null) { + tracker.set("&uid", userId); + } + } + + @ReactMethod + public void allowIDFA(Boolean enabled) + { + Tracker tracker = getTracker(_trackingId); + + if (tracker != null) { + tracker.enableAdvertisingIdCollection(enabled); + } + } + + @ReactMethod + public void trackSocialInteraction(String network, String action, String targetUrl) + { + Tracker tracker = getTracker(_trackingId); + + if (tracker != null) { + tracker.send(new HitBuilders.SocialBuilder() + .setNetwork(network) + .setAction(action) + .setTarget(targetUrl) + .build()); + } + } + @ReactMethod public void setDryRun(Boolean enabled){ GoogleAnalytics analytics = getAnalyticsInstance(); if (analytics != null) { - analytics.setDryRun(enabled); + analytics.setDryRun(enabled); } } } From cbbde4f8f7f003f512108d4ed4232487431fd644 Mon Sep 17 00:00:00 2001 From: Christian Brevik Date: Mon, 25 Jan 2016 20:04:49 +0100 Subject: [PATCH 14/17] readme edit and more js docs --- README.md | 1 - index.js | 45 +++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 0bfc8238f..f562164c0 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,6 @@ Consult [this guide](https://developer.android.com/sdk/installing/adding-package ``` ## Javascript API -At the moment the implementation exposes three methods: ### trackScreenView(screenName) diff --git a/index.js b/index.js index bac6dc973..178de1804 100644 --- a/index.js +++ b/index.js @@ -3,38 +3,75 @@ const GoogleAnalyticsBridge = require("react-native").NativeModules.GoogleAnalyticsBridge; class GoogleAnalytics { + /** + * Track the current screen/view + * @param {String} screenName The name of the current screen + */ static trackScreenView(screenName) { GoogleAnalyticsBridge.trackScreenView(screenName); } + /** + * Track an event that has occured + * @param {String} category The event category + * @param {String} action The event action + * @param {Object} optionalValues An object containing optional label and value + */ static trackEvent(category, action, optionalValues = {}) { GoogleAnalyticsBridge.trackEvent(category, action, optionalValues); } - // static trackPurchase(transactionId, transaction = {}, product = {}) { - // GoogleAnalyticsBridge.trackPurchase(transactionId, transaction, product); - // } - + /** + * Track a purchase event. This uses the Enhanced Ecommerce GA feature. + * @param {Object} product An object with product values + * @param {Object} transaction An object with transaction values + * @param {String} eventCategory The event category, defaults to Ecommerce + * @param {String} eventAction The event action, defaults to Purchase + */ static trackPurchaseEvent(product = {}, transaction = {}, eventCategory = "Ecommerce", eventAction = "Purchase") { GoogleAnalyticsBridge.trackPurchaseEvent(product, transaction, eventCategory, eventAction); } + /** + * Track an exception + * @param {String} error The description of the error + * @param {Boolean} fatal A value indiciating if the error was fatal, defaults to false + */ static trackException(error, fatal = false) { GoogleAnalyticsBridge.trackException(error, fatal); } + /** + * Sets the current userId for tracking. + * @param {String} userId The current userId + */ static setUser(userId) { GoogleAnalyticsBridge.setUser(userId); } + /** + * Sets if IDFA (identifier for advertisers) collection should be enabled + * @param {Boolean} enabled Defaults to true + */ static allowIDFA(enabled = true) { GoogleAnalyticsBridge.allowIDFA(enabled); } + /** + * Track a social interaction, Facebook, Twitter, etc. + * @param {String} network + * @param {String} action + * @param {String} targetUrl + */ static trackSocialInteraction(network, action, targetUrl) { GoogleAnalyticsBridge.trackSocialInteraction(network, action, targetUrl); } + /** + * Sets if the tracker should have dry run enabled. + * If dry run is enabled, no analytics data will be sent to your tracker. + * @param {Boolean} enabled + */ static setDryRun(enabled) { GoogleAnalyticsBridge.setDryRun(enabled); } From 5a1664dc4f87d6e6a9747599a527befa89607621 Mon Sep 17 00:00:00 2001 From: Christian Brevik Date: Tue, 26 Jan 2016 17:25:41 +0100 Subject: [PATCH 15/17] remove the default reference to libAdIdAccess.a --- .../RCTGoogleAnalyticsBridge.xcodeproj/project.pbxproj | 4 ---- 1 file changed, 4 deletions(-) diff --git a/ios/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge.xcodeproj/project.pbxproj b/ios/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge.xcodeproj/project.pbxproj index ac65aaae2..848981a97 100644 --- a/ios/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge.xcodeproj/project.pbxproj +++ b/ios/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge.xcodeproj/project.pbxproj @@ -7,7 +7,6 @@ objects = { /* Begin PBXBuildFile section */ - 9325E7FD1C512C5300EF100D /* libAdIdAccess.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9325E7FC1C512C5300EF100D /* libAdIdAccess.a */; }; A79185CA1C30694E001236A6 /* RCTGoogleAnalyticsBridge.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = A79185C91C30694E001236A6 /* RCTGoogleAnalyticsBridge.h */; }; A79185CC1C30694E001236A6 /* RCTGoogleAnalyticsBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = A79185CB1C30694E001236A6 /* RCTGoogleAnalyticsBridge.m */; }; A79185DE1C3070E8001236A6 /* libGoogleAnalyticsServices.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A79185DD1C3070E8001236A6 /* libGoogleAnalyticsServices.a */; }; @@ -27,7 +26,6 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 9325E7FC1C512C5300EF100D /* libAdIdAccess.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libAdIdAccess.a; sourceTree = ""; }; A79185C61C30694E001236A6 /* libRCTGoogleAnalyticsBridge.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTGoogleAnalyticsBridge.a; sourceTree = BUILT_PRODUCTS_DIR; }; A79185C91C30694E001236A6 /* RCTGoogleAnalyticsBridge.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTGoogleAnalyticsBridge.h; sourceTree = ""; }; A79185CB1C30694E001236A6 /* RCTGoogleAnalyticsBridge.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RCTGoogleAnalyticsBridge.m; sourceTree = ""; }; @@ -50,7 +48,6 @@ buildActionMask = 2147483647; files = ( A79185DE1C3070E8001236A6 /* libGoogleAnalyticsServices.a in Frameworks */, - 9325E7FD1C512C5300EF100D /* libAdIdAccess.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -96,7 +93,6 @@ A79185DA1C3070E8001236A6 /* GAILogger.h */, A79185DB1C3070E8001236A6 /* GAITrackedViewController.h */, A79185DC1C3070E8001236A6 /* GAITracker.h */, - 9325E7FC1C512C5300EF100D /* libAdIdAccess.a */, A79185DD1C3070E8001236A6 /* libGoogleAnalyticsServices.a */, ); path = "google-analytics-lib"; From b32895e94ba10216f7f7fadab2024860ba7629a0 Mon Sep 17 00:00:00 2001 From: Christian Brevik Date: Tue, 26 Jan 2016 17:39:29 +0100 Subject: [PATCH 16/17] added readme info for the optional AdSupport.framework + libAdIdAccess.a --- README.md | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index f562164c0..111de5909 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ These are step 5 and 6 from the iOS installation, and step 4 from the Android in ## Manual installation iOS 1. `npm install --save react-native-google-analytics-bridge` -2. In XCode, right click the Libraries folder under your project ➜ `Add Files to `. +2. In XCode, right-click the Libraries folder under your project ➜ `Add Files to `. 3. Go to `node_modules` ➜ `react-native-google-analytics-bridge` ➜ `ios` ➜ `RCTGoogleAnalyticsBridge` and add the `RCTGoogleAnalyticsBridge.xcodeproj` file. 4. Add libRCTGoogleAnalyticsBridge.a from the linked project to your project properties ➜ "Build Phases" ➜ "Link Binary With Libraries" 5. Next you will have to link a few more SDK framework/libraries which are required by GA (if you do not already have them linked.) Under the same "Link Binary With Libraries", click the + and add the following: @@ -35,12 +35,13 @@ 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 - - **Optional**: If you plan on using the advertising identifier (IDFA), then you also need to add AdSupport.framework 7. 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). +8. **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 directory as in step 3. ## Prerequisites for Android Make sure you have the following SDK packages installed in the Android SDK Manager: @@ -122,7 +123,7 @@ GoogleAnalytics.trackScreenView('Home') * **label:** String * **value:** Number -See the [Google Analytics docs](https://developers.google.com/analytics/devguides/collection/ios/v3/events) for more info +See the [Google Analytics docs](https://developers.google.com/analytics/devguides/collection/ios/v3/events) for more info. ```javascript GoogleAnalytics.trackEvent('testcategory', 'testaction'); @@ -151,7 +152,7 @@ GoogleAnalytics.trackEvent('testcategory', 'testaction', {label: 'v1.0.3', value * **eventCategory (required):** String, defaults to "Ecommerce" * **eventAction (required):** String, defaults to "Purchase" -See the [Google Analytics docs](https://developers.google.com/analytics/devguides/collection/ios/v3/enhanced-ecommerce#measuring-transactions) for more info +See the [Google Analytics docs](https://developers.google.com/analytics/devguides/collection/ios/v3/enhanced-ecommerce#measuring-transactions) for more info. ```javascript GoogleAnalytics.trackPurchase({ @@ -178,7 +179,7 @@ GoogleAnalytics.trackPurchase({ * **error:** String, a description of the exception (up to 100 characters), accepts nil * **fatal (required):** Boolean, indicates whether the exception was fatal, defaults to false -See the [Google Analytics docs](https://developers.google.com/analytics/devguides/collection/ios/v3/exceptions) for more info +See the [Google Analytics docs](https://developers.google.com/analytics/devguides/collection/ios/v3/exceptions) for more info. ```javascript try { @@ -194,7 +195,7 @@ try { * **action (required):** String, social action (e.g. 'Like', 'Share', '+1') * **targetUrl:** String, url of content being shared -See the [Google Analytics](https://developers.google.com/analytics/devguides/collection/ios/v3/social) docs for more info +See the [Google Analytics](https://developers.google.com/analytics/devguides/collection/ios/v3/social) docs for more info. ```javascript GoogleAnalytics.trackSocialInteraction('Twitter', 'Post'); @@ -204,7 +205,7 @@ GoogleAnalytics.trackSocialInteraction('Twitter', 'Post'); * **userId (required):** String, an **anonymous** identifier that complies with Google Analytic's user ID policy -See the [Google Analytics](https://developers.google.com/analytics/devguides/collection/ios/v3/user-id) for more info +See the [Google Analytics](https://developers.google.com/analytics/devguides/collection/ios/v3/user-id) for more info. ```javascript GoogleAnalytics.setUser('12345678'); @@ -212,9 +213,11 @@ GoogleAnalytics.setUser('12345678'); ### allowIDFA(enabled) -* **enabled (required):** Boolean, true to allow IDFA collection, defaults to true +* **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 8 from the installation guide. -See the [Google Analytics](https://developers.google.com/analytics/devguides/collection/ios/v3/campaigns#ios-install) for more info +See the [Google Analytics](https://developers.google.com/analytics/devguides/collection/ios/v3/campaigns#ios-install) for more info. ```javascript GoogleAnalytics.allowIDFA(true); From 688c5b393e9bb6c333bb33b5e14d7121ca5ea7a0 Mon Sep 17 00:00:00 2001 From: Christian Brevik Date: Tue, 26 Jan 2016 18:57:45 +0100 Subject: [PATCH 17/17] minor fixes, more example code --- README.md | 2 +- .../GoogleAnalyticsBridge.java | 4 +-- example/index.android.js | 29 +++++++++++++++++++ example/index.ios.js | 28 ++++++++++++++++++ example/ios/example.xcodeproj/project.pbxproj | 8 ++--- 5 files changed, 64 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 111de5909..dbf2a4fb1 100644 --- a/README.md +++ b/README.md @@ -156,7 +156,7 @@ See the [Google Analytics docs](https://developers.google.com/analytics/devguide ```javascript GoogleAnalytics.trackPurchase({ - id: 'P12345' + id: 'P12345', name: 'Android Warhol T-Shirt', category: 'Apparel/T-Shirts', brand: 'Google', diff --git a/android/src/main/java/com/idehub/GoogleAnalyticsBridge/GoogleAnalyticsBridge.java b/android/src/main/java/com/idehub/GoogleAnalyticsBridge/GoogleAnalyticsBridge.java index cca67e91a..08865dae1 100644 --- a/android/src/main/java/com/idehub/GoogleAnalyticsBridge/GoogleAnalyticsBridge.java +++ b/android/src/main/java/com/idehub/GoogleAnalyticsBridge/GoogleAnalyticsBridge.java @@ -92,7 +92,7 @@ public void trackPurchaseEvent(ReadableMap product, ReadableMap transaction, Str if (tracker != null) { Product ecommerceProduct = new Product() - .setId(product.getString("productId")) + .setId(product.getString("id")) .setName(product.getString("name")) .setCategory(product.getString("category")) .setBrand(product.getString("brand")) @@ -102,7 +102,7 @@ public void trackPurchaseEvent(ReadableMap product, ReadableMap transaction, Str .setQuantity(product.getInt("quantity")); ProductAction productAction = new ProductAction(ProductAction.ACTION_PURCHASE) - .setTransactionId(transaction.getString("transactionId")) + .setTransactionId(transaction.getString("id")) .setTransactionAffiliation(transaction.getString("affiliation")) .setTransactionRevenue(transaction.getDouble("revenue")) .setTransactionTax(transaction.getDouble("tax")) diff --git a/example/index.android.js b/example/index.android.js index b92760c44..aec9b2cf1 100644 --- a/example/index.android.js +++ b/example/index.android.js @@ -22,6 +22,35 @@ var example = React.createClass({ GoogleAnalytics.setDryRun(false); GoogleAnalytics.trackEvent('testcategory', 'Hello Android', { label: "notdry", value: 1 }); + GoogleAnalytics.trackPurchaseEvent( + { + id: 'P12345', + name: 'Android Warhol T-Shirt', + category: 'Apparel/T-Shirts', + brand: 'Google', + variant: 'Black', + price: 29.20, + quantity: 1, + couponCode: 'APPARELSALE' + }, { + id: 'T12345', + affiliation: 'Google Store - Online', + revenue: 37.39, + tax: 2.85, + shipping: 5.34, + couponCode: 'SUMMER2013' + } + ); + + + GoogleAnalytics.trackException("This is an error message", false); + + GoogleAnalytics.trackSocialInteraction('Twitter', 'Post'); + + GoogleAnalytics.setUser('12345678'); + + GoogleAnalytics.allowIDFA(true); + return ( diff --git a/example/index.ios.js b/example/index.ios.js index 12a784407..49d375fd6 100644 --- a/example/index.ios.js +++ b/example/index.ios.js @@ -23,6 +23,34 @@ var example = React.createClass({ GoogleAnalytics.setDryRun(false); GoogleAnalytics.trackEvent('testcategory', 'Hello iOS', { label: "notdry", value: 1 }); + GoogleAnalytics.trackPurchaseEvent( + { + id: 'P12345', + name: 'Android Warhol T-Shirt', + category: 'Apparel/T-Shirts', + brand: 'Apple', + variant: 'Black', + price: 29.20, + quantity: 1, + couponCode: 'APPARELSALE' + }, { + id: 'T12345', + affiliation: 'Apple Store - Online', + revenue: 37.39, + tax: 2.85, + shipping: 5.34, + couponCode: 'SUMMER2013' + } + ); + + GoogleAnalytics.trackException("This is an error message", false); + + GoogleAnalytics.trackSocialInteraction('Twitter', 'Post'); + + GoogleAnalytics.setUser('12345678'); + + GoogleAnalytics.allowIDFA(true); + return ( diff --git a/example/ios/example.xcodeproj/project.pbxproj b/example/ios/example.xcodeproj/project.pbxproj index 77242976e..d9c3108fd 100644 --- a/example/ios/example.xcodeproj/project.pbxproj +++ b/example/ios/example.xcodeproj/project.pbxproj @@ -23,11 +23,11 @@ 146834051AC3E58100842450 /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 146834041AC3E56700842450 /* libReact.a */; }; 832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 832341B51AAA6A8300B99B32 /* libRCTText.a */; }; A79186011C30977F001236A6 /* libRCTGoogleAnalyticsBridge.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A79186001C308FDC001236A6 /* libRCTGoogleAnalyticsBridge.a */; }; - A79186031C309837001236A6 /* AdSupport.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A79186021C309837001236A6 /* AdSupport.framework */; }; A79186051C30983F001236A6 /* CoreData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A79186041C30983F001236A6 /* CoreData.framework */; }; A79186071C309848001236A6 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A79186061C309848001236A6 /* SystemConfiguration.framework */; }; A79186091C30984E001236A6 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = A79186081C30984E001236A6 /* libz.tbd */; }; A791860B1C309879001236A6 /* libsqlite3.0.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = A791860A1C309879001236A6 /* libsqlite3.0.tbd */; }; + A7B8CAC11C57E98C004DAC02 /* AdSupport.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7B8CAC01C57E98C004DAC02 /* AdSupport.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -140,11 +140,11 @@ 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = "../node_modules/react-native/Libraries/LinkingIOS/RCTLinking.xcodeproj"; sourceTree = ""; }; 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = "../node_modules/react-native/Libraries/Text/RCTText.xcodeproj"; sourceTree = ""; }; A79185FB1C308FDC001236A6 /* RCTGoogleAnalyticsBridge.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTGoogleAnalyticsBridge.xcodeproj; path = ../../ios/RCTGoogleAnalyticsBridge/RCTGoogleAnalyticsBridge.xcodeproj; sourceTree = ""; }; - A79186021C309837001236A6 /* AdSupport.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AdSupport.framework; path = System/Library/Frameworks/AdSupport.framework; sourceTree = SDKROOT; }; A79186041C30983F001236A6 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; }; A79186061C309848001236A6 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; A79186081C30984E001236A6 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; A791860A1C309879001236A6 /* libsqlite3.0.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.0.tbd; path = usr/lib/libsqlite3.0.tbd; sourceTree = SDKROOT; }; + A7B8CAC01C57E98C004DAC02 /* AdSupport.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AdSupport.framework; path = System/Library/Frameworks/AdSupport.framework; sourceTree = SDKROOT; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -159,12 +159,12 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + A7B8CAC11C57E98C004DAC02 /* AdSupport.framework in Frameworks */, A79186011C30977F001236A6 /* libRCTGoogleAnalyticsBridge.a in Frameworks */, A791860B1C309879001236A6 /* libsqlite3.0.tbd in Frameworks */, A79186091C30984E001236A6 /* libz.tbd in Frameworks */, A79186071C309848001236A6 /* SystemConfiguration.framework in Frameworks */, A79186051C30983F001236A6 /* CoreData.framework in Frameworks */, - A79186031C309837001236A6 /* AdSupport.framework in Frameworks */, 146834051AC3E58100842450 /* libReact.a in Frameworks */, 00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */, 00C302E71ABCBA2D00DB3ED1 /* libRCTGeolocation.a in Frameworks */, @@ -313,11 +313,11 @@ 83CBB9F61A601CBA00E9B192 = { isa = PBXGroup; children = ( + A7B8CAC01C57E98C004DAC02 /* AdSupport.framework */, A791860A1C309879001236A6 /* libsqlite3.0.tbd */, A79186081C30984E001236A6 /* libz.tbd */, A79186061C309848001236A6 /* SystemConfiguration.framework */, A79186041C30983F001236A6 /* CoreData.framework */, - A79186021C309837001236A6 /* AdSupport.framework */, 13B07FAE1A68108700A75B9A /* example */, 832341AE1AAA6A7D00B99B32 /* Libraries */, 00E356EF1AD99517003FC87E /* exampleTests */,