Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrate ump #2

Merged
merged 5 commits into from
Jun 15, 2024
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
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ The [example app](/example/) demonstrates usage of the library. You need to run

It is configured to use the local version of the library, so any changes you make to the library's source code will be reflected in the example app. Changes to the library's JavaScript code will be reflected in the example app without a rebuild, but native code changes will require a rebuild of the example app.

If you want to use Android Studio or XCode to edit the native code, you can open the `example/android` or `example/ios` directories respectively in those editors. To edit the Objective-C or Swift files, open `example/ios/AdmanagerMobileAdsExample.xcworkspace` in XCode and find the source files at `Pods > Development Pods > react-native-admanager-mobile-ads`.
If you want to use Android Studio or XCode to edit the native code, you can open the `example/android` or `example/ios` directories respectively in those editors. To edit the Objective-C or Swift files, open `example/ios/AdManagerMobileAdsExample.xcworkspace` in XCode and find the source files at `Pods > Development Pods > react-native-admanager-mobile-ads`.

To edit the Java or Kotlin files, open `example/android` in Android studio and find the source files at `react-native-admanager-mobile-ads` under `Android`.

Expand Down Expand Up @@ -73,7 +73,7 @@ yarn clean
To confirm that the app is running with the new architecture, you can check the Metro logs for a message like this:

```sh
Running "AdmanagerMobileAdsExample" with {"fabric":true,"initialProps":{"concurrentRoot":true},"rootTag":1}
Running "AdManagerMobileAdsExample" with {"fabric":true,"initialProps":{"concurrentRoot":true},"rootTag":1}
```

Note the `"fabric":true` and `"concurrentRoot":true` properties.
Expand Down
214 changes: 214 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,220 @@ yarn example android
cd ./example/android && ./gradlew generateCodegenArtifactsFromSchema && cd ../../ && yarn example android
```

## European User Consent

Under the Google EU User Consent Policy, you must make certain disclosures to your users in the European Economic Area (EEA) and obtain their consent to use cookies or other local storage, where legally required, and to use personal data (such as AdID) to serve ads. This policy reflects the requirements of the EU ePrivacy Directive and the General Data Protection Regulation (GDPR).

The React Native AdManager Mobile Ads module provides out of the box support for helping to manage your users consent within your application. The AdsConsent helper which comes with the module wraps the Google UMP SDK for both Android & iOS, and provides a single JavaScript interface for both platforms.

> This is mostly copied from https://docs.page/invertase/react-native-admanager-mobile-ads/european-user-consent. as invertase did a great work on it.

### Understanding AdMob Ads

Ads served by Google can be categorized as personalized or non-personalized, both requiring consent from users in the EEA. By default,
ad requests to Google serve personalized ads, with ad selection based on the user's previously collected data. Users outside of the EEA do not require consent.

> The `AdsConsent` helper only provides you with the tools for requesting consent, it is up to the developer to ensure the consent status is reflected throughout the app.

### Handling consent

To setup and configure ads consent collection, first of all:

- Enable and configure GDPR and IDFA messaging in the [Privacy & messaging section of AdManager's Web Console](https://admanager.google.com/).

- For Android, add the following rule into
`android/app/proguard-rules.pro`:
```
-keep class com.google.android.gms.internal.consent_sdk.** { *; }
```
- For Expo users, add extraProguardRules property to `app.json` file following this guide [Expo](https://docs.expo.dev/versions/latest/sdk/build-properties/#pluginconfigtypeandroid):

```json
{
"expo": {
"plugins": [
[
"expo-build-properties",
{
"android": {
"extraProguardRules": "-keep class com.google.android.gms.internal.consent_sdk.** { *; }"
}
}
]
]
}
}
```

You'll need to generate a new development build before using it.

### Requesting consent information

It is recommended you request consent information each time your application starts to determine if the consent modal should be shown, e.g. due to provider changes.

The `AdsConsent` helper provides a promise based method to return the users consent status called `requestInfoUpdate`.

```js
import { AdsConsent } from "react-native-admanager-mobile-ads";

const consentInfo = await AdsConsent.requestConsentInfoUpdate();
```

The method returns an `AdsConsentInfo` interface, which provides information about consent form availability and the users consent status:

- **status**: The status can be one of 4 outcomes:
- `UNKNOWN`: Unknown consent status.
- `REQUIRED`: User consent required but not yet obtained.
- `NOT_REQUIRED`: User consent not required.
- `OBTAINED`: User consent already obtained.
- **isConsentFormAvailable**: A boolean value. If `true` a consent form is available.

**Note:** The return status of this call is the status of _form presentation and response collection_, **not
the the actual user consent**. It simply indicates if you now have a consent response to decode.
(_i.e._ if user consent is **required**, the form has been presented, and user has
**denied** the consent, the status returned by this method will be `OBTAINED`,
and not `REQUIRED` as some may expect). To check the actual consent status
see [Inspecting consent choices] below.

### Gathering user consent

You should request an update of the user's consent information at every app launch, using `requestInfoUpdate`.
This determines whether your user needs to provide consent if they haven't done so already, or if their consent has expired.

After you have received the most up-to-date consent status, call `loadAndShowConsentFormIfRequired` to load a consent form.
If the consent status is required, the SDK loads a form and immediately presents it.
The completion handler is called after the form is dismissed. If consent is not required, the completion handler is called immediately.

Before requesting ads in your app, check if you have obtained consent from the user using `canRequestAds`.
If an error occurs during the consent gathering process, you should still attempt to request ads.
The UMP SDK uses the consent status from the previous session.

```js
import {
AdManager,
AdsConsent,
AdsConsentStatus
} from "react-native-admanager-mobile-ads";

let isMobileAdsStartCalled = false;

// Request an update for the consent information.
AdsConsent.requestInfoUpdate().then(() => {
AdsConsent.loadAndShowConsentFormIfRequired().then((adsConsentInfo) => {
// Consent has been gathered.
if (adsConsentInfo.canRequestAds) {
startGoogleMobileAdsSDK();
}
});
});

// Check if you can initialize the Google Mobile Ads SDK in parallel
// while checking for new consent information. Consent obtained in
// the previous session can be used to request ads.
// So you can start loading ads as soon as possible after your app launches.
const { canRequestAds } = await AdsConsent.getConsentInfo();
if (canRequestAds) {
startGoogleMobileAdsSDK();
}

async function startGoogleMobileAdsSDK() {
if (isMobileAdsStartCalled) return;

isMobileAdsStartCalled = true;

// (Optional, iOS) Handle Apple's App Tracking Transparency manually.
const gdprApplies = await AdsConsent.getGdprApplies();
const hasConsentForPurposeOne =
gdprApplies && (await AdsConsent.getPurposeConsents()).startsWith("1");
if (!gdprApplies || hasConsentForPurposeOne) {
// Request ATT...
}

// Initialize the Google Mobile Ads SDK.
await AdManager.start();

// Request an ad...
}
```

> Do not persist the status. You could however store this locally in application state (e.g. React Context) and update the status on every app launch as it may change.

### Inspecting consent choices

The AdsConsentStatus tells you if you should show the modal to a user or not. Often times you want to run logic based on the user's choices though.
Especially since the Google Mobile Ads SDK won't show any ads if the user didn't give consent to store and/or access information on the device.
This library exports a method that returns some of the most relevant consent flags to handle common use cases.

```js
import { AdsConsent } from "react-native-admanager-mobile-ads";

const {
activelyScanDeviceCharacteristicsForIdentification,
applyMarketResearchToGenerateAudienceInsights,
createAPersonalisedAdsProfile,
createAPersonalisedContentProfile,
developAndImproveProducts,
measureAdPerformance,
measureContentPerformance,
selectBasicAds,
selectPersonalisedAds,
selectPersonalisedContent,
storeAndAccessInformationOnDevice,
usePreciseGeolocationData
} = await AdsConsent.getUserChoices();

if (storeAndAccessInformationOnDevice === false) {
/**
* The user declined consent for purpose 1,
* the Google Mobile Ads SDK won't serve ads.
*/
}
```

**Note:** Don't try to use this functionality to enforce user consent on iOS,
this will likely result in failed app review upon submission to Apple Store, based on [review guideline 3.2.2.vi](https://developer.apple.com/app-store/review/guidelines/#3.2.2):

> ...Apps should not require users to rate the app, review the app, watch videos, download other apps, tap on advertisements, enable tracking...

### Testing

When developing the consent flow, the behavior of the `AdsConsent` responses may not be reliable due to the environment
(e.g. using an emulator vs real device). It is possible to set a debug location to test the various responses from the
UMP SDK.

If using a real device, ensure you add it to the list of allowed devices by passing the device ID, which can be obtained from native logs or using a library
such as [react-native-device-info](https://github.com/react-native-community/react-native-device-info), to `testDeviceIdentifiers`.

> Emulators are automatically whitelisted.

To set a debug location, use the `debugGeography` key. It accepts 3 values:

- **DISABLED**: Removes any previous debug locations.
- **EEA**: Set the test device to be within the EEA.
- **NOT_EEA**: Set the test device to be outside of the EEA.

For example:

```js
import {
AdsConsent,
AdsConsentDebugGeography
} from "react-native-admanager-mobile-ads";

const consentInfo = await AdsConsent.requestInfoUpdate({
debugGeography: AdsConsentDebugGeography.EEA,
testDeviceIdentifiers: ["TEST-DEVICE-HASHED-ID"]
});
```

It is possible to reset the UMP state for test devices. To reset the ATT state you have to delete and reinstall your app though.

```js
import { AdsConsent } from "react-native-admanager-mobile-ads";

AdsConsent.reset();
```

## Contributing

See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow.
Expand Down
6 changes: 3 additions & 3 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ if (isNewArchitectureEnabled()) {
}

def getExtOrDefault(name) {
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["AdmanagerMobileAds_" + name]
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["AdManagerMobileAds_" + name]
}

def getExtOrIntegerDefault(name) {
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["AdmanagerMobileAds_" + name]).toInteger()
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["AdManagerMobileAds_" + name]).toInteger()
}

def supportsNamespace() {
Expand Down Expand Up @@ -116,7 +116,7 @@ dependencies {
if (isNewArchitectureEnabled()) {
react {
jsRootDir = file("../src/")
libraryName = "AdmanagerMobileAds"
libraryName = "AdManagerMobileAds"
codegenJavaPackageName = "com.admanagermobileads"
}
}
10 changes: 5 additions & 5 deletions android/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
AdmanagerMobileAds_kotlinVersion=1.7.0
AdmanagerMobileAds_minSdkVersion=21
AdmanagerMobileAds_targetSdkVersion=31
AdmanagerMobileAds_compileSdkVersion=31
AdmanagerMobileAds_ndkversion=21.4.7075529
AdManagerMobileAds_kotlinVersion=1.7.0
AdManagerMobileAds_minSdkVersion=21
AdManagerMobileAds_targetSdkVersion=31
AdManagerMobileAds_compileSdkVersion=31
AdManagerMobileAds_ndkversion=21.4.7075529
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.admanagermobileads;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.facebook.react.bridge.NativeModule;
Expand All @@ -8,16 +9,29 @@
import com.facebook.react.module.model.ReactModuleInfoProvider;
import com.facebook.react.TurboReactPackage;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class AdmanagerMobileAdsPackage extends TurboReactPackage {
public class AdManagerMobileAdsPackage extends TurboReactPackage {

@NonNull
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new AdManagerMobileAdsModule(reactContext));
modules.add(new AdManagerMobileAdsConsentModule(reactContext));
return modules;
}

@Nullable
@Override
public NativeModule getModule(String name, ReactApplicationContext reactContext) {
if (name.equals(AdmanagerMobileAdsModule.NAME)) {
return new AdmanagerMobileAdsModule(reactContext);
if (name.equals(AdManagerMobileAdsModule.NAME)) {
return new AdManagerMobileAdsModule(reactContext);
} else if (name.equals(AdManagerMobileAdsConsentModule.NAME)) {
return new AdManagerMobileAdsConsentModule(reactContext);
} else {
return null;
}
Expand All @@ -29,16 +43,27 @@ public ReactModuleInfoProvider getReactModuleInfoProvider() {
final Map<String, ReactModuleInfo> moduleInfos = new HashMap<>();
boolean isTurboModule = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
moduleInfos.put(
AdmanagerMobileAdsModule.NAME,
AdManagerMobileAdsModule.NAME,
new ReactModuleInfo(
AdmanagerMobileAdsModule.NAME,
AdmanagerMobileAdsModule.NAME,
AdManagerMobileAdsModule.NAME,
AdManagerMobileAdsModule.NAME,
false, // canOverrideExistingModule
false, // needsEagerInit
true, // hasConstants
false, // isCxxModule
isTurboModule // isTurboModule
));
moduleInfos.put(
AdManagerMobileAdsConsentModule.NAME,
new ReactModuleInfo(
AdManagerMobileAdsConsentModule.NAME,
AdManagerMobileAdsConsentModule.NAME,
false, // canOverrideExistingModule
false, // needsEagerInit
true, // hasConstants
false, // isCxxModule
isTurboModule // isTurboModule
));
return moduleInfos;
};
}
Expand Down
Loading
Loading