diff --git a/README.md b/README.md index 758a9f48..71c8d1ab 100644 --- a/README.md +++ b/README.md @@ -33,13 +33,7 @@ Note that for iOS, it's [recommended you use Cocoapods](https://developers.faceb #### For iOS: -1. Add the following Pod to your Podfile: - -``` -pod 'FBAudienceNetwork', '~> 5.1.0' -``` - -2. Run `pod install` +1. Run `pod install` in the `ios/` directory If you didn't use Cocoapods to integrate the Facebook SDK, you'll need to manually add the audience network framework file to your project. @@ -385,6 +379,47 @@ AdSettings.setUrlPrefix('...'); **Note:** This method should never be used in production +### getTrackingStatus + +Gets the current Tracking API status. As of iOS 14, Apple requires apps to only enable tracking (advertiser ID collection) when the user has granted tracking permissions. + +> Requires iOS 14. On Android and iOS versions below 14, this will always return `'unavailable'`. + +```js +const trackingStatus = await AdSettings.getTrackingStatus(); +if (trackingStatus === 'authorized' || trackingStatus === 'unavailable') { + AdSettings.setAdvertiserIDCollectionEnabled(true); +} +``` + +The tracking status can return one of the following values: + +* `'unavailable'`: The tracking API is not available on the current device. That's the case on Android devices and iPhones below iOS 14. +* `'denied'`: The user has explicitly denied permission to track. You'd want to respect that and disable [advertiser ID collection](#setAdvertiserIDCollectionEnabled). +* `'authorized'`: The user has granted permission to track. You can now enable [advertiser ID collection](#setAdvertiserIDCollectionEnabled). +* `'restricted'`: The tracking permission alert cannot be shown, because the device is restricted. See [`ATTrackingManager.AuthorizationStatus.restricted`](https://developer.apple.com/documentation/apptrackingtransparency/attrackingmanager/authorizationstatus/restricted) for more information. +* `'not-determined'`: The user has not been asked to grant tracking permissions yet. Call `requestTrackingPermission()`. + +### requestTrackingPermission + +Requests permission to track the user. Requires an [`NSUserTrackingUsageDescription`](https://developer.apple.com/documentation/bundleresources/information_property_list/nsusertrackingusagedescription) key in your `Info.plist`. (See [iOS 14 Tracking API](https://developer.apple.com/documentation/apptrackingtransparency)) + +> Requires iOS 14. On Android and iOS versions below 14, this will always return `'unavailable'`. + +```js +const trackingStatus = await AdSettings.requestTrackingPermission(); +if (trackingStatus === 'authorized' || trackingStatus === 'unavailable') + AdSettings.setAdvertiserIDCollectionEnabled(true); +``` + +### setAdvertiserIDCollectionEnabled + +Enables or disables automatic advertiser ID collection. Since the iOS 14 API was introduced, you might want to disable advertiser ID collection per default (in `Info.plist`), and only enable it once the user has granted tracking permissions. + +```js +AdSettings.setAdvertiserIDCollectionEnabled(true); +``` + ## Running the example In order to see ads you will have to create your own `placementId` and use it instead of the one provided in the examples. This is our internal set up that doesn't work for any developers outside of Callstack.io organisation. This is because of Facebook not showing test ads to outside collaborators in the development mode. diff --git a/android/app/build.gradle b/android/app/build.gradle index 4c80f649..5d60f716 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -27,5 +27,6 @@ repositories { dependencies { implementation 'com.facebook.react:react-native:+' implementation "com.android.support:recyclerview-v7:${safeExtGet('supportLibVersion', '26.1.0')}" - implementation 'com.facebook.android:audience-network-sdk:5.+' + implementation 'com.facebook.android:audience-network-sdk:6.2.+' + implementation 'com.facebook.android:facebook-android-sdk:5.15.+' } diff --git a/android/app/src/main/java/suraj/tiwari/reactnativefbads/AdSettingsManager.java b/android/app/src/main/java/suraj/tiwari/reactnativefbads/AdSettingsManager.java index b91f8c9f..f025fd42 100644 --- a/android/app/src/main/java/suraj/tiwari/reactnativefbads/AdSettingsManager.java +++ b/android/app/src/main/java/suraj/tiwari/reactnativefbads/AdSettingsManager.java @@ -1,9 +1,9 @@ package suraj.tiwari.reactnativefbads; -import android.content.Context; import android.content.SharedPreferences; import android.util.Log; +import com.facebook.FacebookSdk; import com.facebook.ads.AdSettings; import com.facebook.react.bridge.LifecycleEventListener; import com.facebook.react.bridge.ReactApplicationContext; @@ -52,7 +52,7 @@ public void setLogLevel(String logLevel) { @ReactMethod public void setIsChildDirected(boolean isChildDirected) { - AdSettings.setIsChildDirected(isChildDirected); + AdSettings.setMixedAudience(isChildDirected); mIsChildDirected = isChildDirected; } @@ -68,19 +68,24 @@ public void setUrlPrefix(String urlPrefix) { mUrlPrefix = urlPrefix; } + @ReactMethod + public void setAdvertiserIDCollectionEnabled(boolean enabled) { + FacebookSdk.setAdvertiserIDCollectionEnabled(enabled); + } + private void restoreSettings() { for (String hash: mTestDeviceHashes) { AdSettings.addTestDevice(hash); } - AdSettings.setIsChildDirected(mIsChildDirected); + AdSettings.setMixedAudience(mIsChildDirected); AdSettings.setMediationService(mMediationService); AdSettings.setUrlPrefix(mUrlPrefix); } private void clearSettings() { AdSettings.clearTestDevices(); - AdSettings.setIsChildDirected(false); + AdSettings.setMixedAudience(false); AdSettings.setMediationService(null); AdSettings.setUrlPrefix(null); } diff --git a/example/android/build.gradle b/example/android/build.gradle index 49569e4d..78d7f427 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -7,9 +7,10 @@ buildscript { url 'https://maven.google.com/' name 'Google' } + google() } dependencies { - classpath 'com.android.tools.build:gradle:2.3.3' + classpath 'com.android.tools.build:gradle:4.0.1' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties index 81a86e21..19f16157 100644 --- a/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/example/android/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ +#Thu Dec 10 14:52:09 CET 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.5.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip diff --git a/ios/ReactNativeAdsFacebook/EXAdSettingsManager.m b/ios/ReactNativeAdsFacebook/EXAdSettingsManager.m index f63cc673..f63e0e03 100644 --- a/ios/ReactNativeAdsFacebook/EXAdSettingsManager.m +++ b/ios/ReactNativeAdsFacebook/EXAdSettingsManager.m @@ -5,6 +5,9 @@ #import #import #import +#import + +#import @implementation RCTConvert (EXNativeAdView) @@ -100,6 +103,33 @@ - (void)setBridge:(RCTBridge *)bridge _urlPrefix = urlPrefix; } + +RCT_EXPORT_METHOD(getTrackingStatus:(RCTPromiseResolveBlock)resolve rejector:(RCTPromiseRejectBlock)reject) +{ + if (@available(iOS 14, *)) { + resolve([EXAdSettingsManager convertTrackingStatusToString:[ATTrackingManager trackingAuthorizationStatus]]); + } else { + resolve(@"unavailable"); + } +} + +RCT_EXPORT_METHOD(requestTrackingPermission:(RCTPromiseResolveBlock)resolve rejector:(RCTPromiseRejectBlock)reject) +{ + if (@available(iOS 14, *)) { + [ATTrackingManager requestTrackingAuthorizationWithCompletionHandler:^(ATTrackingManagerAuthorizationStatus status) { + resolve([EXAdSettingsManager convertTrackingStatusToString:status]); + }]; + } else { + resolve(@"unavailable"); + } +} + +RCT_EXPORT_METHOD(setAdvertiserIDCollectionEnabled:(BOOL)enabled) +{ + [FBSDKSettings setAdvertiserIDCollectionEnabled:enabled]; +} + + - (void)bridgeDidForeground:(NSNotification *)notification { [FBAdSettings setIsChildDirected:_isChildDirected]; @@ -128,4 +158,17 @@ - (NSDictionary *)constantsToExport return @{ @"currentDeviceHash": [FBAdSettings testDeviceHash] }; } ++ (NSString *) convertTrackingStatusToString:(ATTrackingManagerAuthorizationStatus) status API_AVAILABLE(ios(14)) { + switch (status) { + case ATTrackingManagerAuthorizationStatusDenied: + return @"denied"; + case ATTrackingManagerAuthorizationStatusAuthorized: + return @"authorized"; + case ATTrackingManagerAuthorizationStatusRestricted: + return @"restricted"; + case ATTrackingManagerAuthorizationStatusNotDetermined: + return @"not-determined"; + } +} + @end diff --git a/ios/ReactNativeAdsFacebook/EXNativeAdManager.m b/ios/ReactNativeAdsFacebook/EXNativeAdManager.m index 3c897115..3a4e351b 100644 --- a/ios/ReactNativeAdsFacebook/EXNativeAdManager.m +++ b/ios/ReactNativeAdsFacebook/EXNativeAdManager.m @@ -56,31 +56,31 @@ + (BOOL)requiresMainQueueSetup FBMediaView *mediaView = nil; FBAdIconView *adIconView = nil; EXNativeAdView *nativeAdView = nil; - + if ([viewRegistry objectForKey:mediaViewTag] == nil) { reject(@"E_NO_VIEW_FOR_TAG", @"Could not find mediaView", nil); return; } - + if ([viewRegistry objectForKey:nativeAdViewTag] == nil) { reject(@"E_NO_NATIVEAD_VIEW", @"Could not find nativeAdView", nil); return; } - + if ([[viewRegistry objectForKey:mediaViewTag] isKindOfClass:[FBMediaView class]]) { mediaView = (FBMediaView *)[viewRegistry objectForKey:mediaViewTag]; } else { reject(@"E_INVALID_VIEW_CLASS", @"View returned for passed media view tag is not an instance of FBMediaView", nil); return; } - + if ([[viewRegistry objectForKey:nativeAdViewTag] isKindOfClass:[EXNativeAdView class]]) { nativeAdView = (EXNativeAdView *)[viewRegistry objectForKey:nativeAdViewTag]; } else { reject(@"E_INVALID_VIEW_CLASS", @"View returned for passed native ad view tag is not an instance of EXNativeAdView", nil); return; } - + if ([viewRegistry objectForKey:adIconViewTag]) { if ([[viewRegistry objectForKey:adIconViewTag] isKindOfClass:[FBAdIconView class]]) { adIconView = (FBAdIconView *)[viewRegistry objectForKey:adIconViewTag]; @@ -89,7 +89,7 @@ + (BOOL)requiresMainQueueSetup return; } } - + NSMutableArray *clickableViews = [NSMutableArray new]; for (id tag in tags) { if ([viewRegistry objectForKey:tag]) { @@ -99,7 +99,7 @@ + (BOOL)requiresMainQueueSetup return; } } - + [nativeAdView registerViewsForInteraction:mediaView adIcon:adIconView clickableViews:clickableViews]; resolve(@[]); }]; @@ -112,12 +112,12 @@ + (BOOL)requiresMainQueueSetup forNumAdsRequested:[adsToRequest intValue]]; _myAdChoiceViewPlacementId = placementId; - + [adsManager setDelegate:self]; dispatch_async(dispatch_get_main_queue(), ^{ [adsManager loadAds]; - [_adsManagers setValue:adsManager forKey:placementId]; + [self->_adsManagers setValue:adsManager forKey:placementId]; }); } @@ -143,7 +143,7 @@ - (void)nativeAdsLoaded [_adsManagers enumerateKeysAndObjectsUsingBlock:^(NSString* key, FBNativeAdsManager* adManager, __unused BOOL* stop) { [adsManagersState setValue:@([adManager isValid]) forKey:key]; }]; - + EXNativeAdEmitter *nativeAdEmitter = [_bridge moduleForClass:[EXNativeAdEmitter class]]; [nativeAdEmitter sendManagersState:adsManagersState]; } diff --git a/src/AdSettings.ts b/src/AdSettings.ts index ba8b425c..27341c4f 100644 --- a/src/AdSettings.ts +++ b/src/AdSettings.ts @@ -1,4 +1,4 @@ -import { NativeModules } from 'react-native'; +import { NativeModules, Platform } from 'react-native'; const { CTKAdSettingsManager } = NativeModules; @@ -10,6 +10,8 @@ type SDKLogLevel = | 'error' | 'notification'; +export type TrackingStatus = 'unavailable' | 'denied' | 'authorized' | 'restricted' | 'not-determined'; + export default { /** * Contains hash of the device id @@ -53,5 +55,33 @@ export default { */ setUrlPrefix(urlPrefix: string) { CTKAdSettingsManager.setUrlPrefix(urlPrefix); + }, + + /** + * Requests permission to track the user. + * + * Requires a [`NSUserTrackingUsageDescription`](https://developer.apple.com/documentation/bundleresources/information_property_list/nsusertrackingusagedescription) in your `Info.plist` + * + * @platform iOS 14 + */ + async requestTrackingPermission(): Promise { + if (Platform.OS !== 'ios') return 'unavailable'; + return await CTKAdSettingsManager.requestTrackingPermission(); + }, + /** + * Gets the current tracking status. + * + * @platform iOS 14 + */ + async getTrackingStatus(): Promise { + if (Platform.OS !== 'ios') return 'unavailable'; + return await CTKAdSettingsManager.getTrackingStatus(); + }, + + /** + * Enable or disable the automatic Advertiser ID Collection. On iOS 14 it is recommended to only enable automatic Advertiser ID Collection when the user has granted permission to track. (@see `requestTrackingPermission()`) + */ + setAdvertiserIDCollectionEnabled(enabled: boolean): void { + CTKAdSettingsManager.setAdvertiserIDCollectionEnabled(enabled); } };