Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 42 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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.
Expand Down
3 changes: 2 additions & 1 deletion android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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.+'
}
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -52,7 +52,7 @@ public void setLogLevel(String logLevel) {

@ReactMethod
public void setIsChildDirected(boolean isChildDirected) {
AdSettings.setIsChildDirected(isChildDirected);
AdSettings.setMixedAudience(isChildDirected);
mIsChildDirected = isChildDirected;
}

Expand All @@ -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);
}
Expand Down
3 changes: 2 additions & 1 deletion example/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion example/android/gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -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
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we make sure this make the library usable for RN lower than 0.64?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well I've tried to set it to 4.2, but I'm getting the following error (in the example/ app):
image

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, according to the rn upgrade helper, RN 0.63 uses gradle-6.2-all.zip so I'd say 6.1.1 is fine

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mhm, I see that 6.2 is used on RN 0.63, so it's not too bad. Would be cool to test on lower RN versions (but up to 0.61 max). Will require a major bump anyway

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0.61 uses gradle 5, 0.62 uses gradle 6 - I don't have a lot of android experience, but shouldn't it build either way?

43 changes: 43 additions & 0 deletions ios/ReactNativeAdsFacebook/EXAdSettingsManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
#import <React/RCTUtils.h>
#import <React/RCTConvert.h>
#import <FBAudienceNetwork/FBAudienceNetwork.h>
#import <AppTrackingTransparency/AppTrackingTransparency.h>

#import <FBSDKCoreKit/FBSDKSettings.h>

@implementation RCTConvert (EXNativeAdView)

Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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
20 changes: 10 additions & 10 deletions ios/ReactNativeAdsFacebook/EXNativeAdManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand All @@ -89,7 +89,7 @@ + (BOOL)requiresMainQueueSetup
return;
}
}

NSMutableArray<UIView *> *clickableViews = [NSMutableArray new];
for (id tag in tags) {
if ([viewRegistry objectForKey:tag]) {
Expand All @@ -99,7 +99,7 @@ + (BOOL)requiresMainQueueSetup
return;
}
}

[nativeAdView registerViewsForInteraction:mediaView adIcon:adIconView clickableViews:clickableViews];
resolve(@[]);
}];
Expand All @@ -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];
});
}

Expand All @@ -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];
}
Expand Down
32 changes: 31 additions & 1 deletion src/AdSettings.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { NativeModules } from 'react-native';
import { NativeModules, Platform } from 'react-native';

const { CTKAdSettingsManager } = NativeModules;

Expand All @@ -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
Expand Down Expand Up @@ -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<TrackingStatus> {
if (Platform.OS !== 'ios') return 'unavailable';
return await CTKAdSettingsManager.requestTrackingPermission();
},
/**
* Gets the current tracking status.
*
* @platform iOS 14
*/
async getTrackingStatus(): Promise<TrackingStatus> {
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 {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yup, let's make sure this is documented in the README or so

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright, everything's documented in the README now

CTKAdSettingsManager.setAdvertiserIDCollectionEnabled(enabled);
}
};