Logo
Published on
ยท12 min read

Applying AdMob GDPR in React Native (Android)

I apologize in advance for any awkward expressions in English.

English is not my native language, and I have relied on ChatGPT's assistance to proceed with the translation.

Overview

AdMob keeps warning to display GDPR messages.

Since creating GDPR messages in AdSense was straightforward, I assumed it would be similar for AdMob. However, AdMob required additional code in the application to directly check for consent. ๐Ÿ˜ฑ

I'll share the details of what I implemented in the Simple Vocab Buddy app.

For the implementation on Android, you can refer to Applying AdMob GDPR on Android.

๐Ÿ›‘ Caution

โ— I do not assume legal responsibility. โ—

I implemented this based on Google documentation, sample codes, and searches. However, there may be incorrect interpretations regarding policies or implementations. Please use this as a reference only.

Although I confirmed normal operation in test mode on a test device, I did not verify the actual behavior on devices in the EEA region. Additionally, I have not verified its operation on iOS.

GDPR Message Setup

Go to Google AdMob > GDPR > Click on [Create Message] button.

Configure the settings according to your app. Here are my settings.

Create Message
  • My apps: I selected the app to apply the message to.
  • Language: I specified 'English' as the language for displaying the message.
  • Not consenting: Enabled
  • Close (Not consenting): Disabled

After creating and publishing the message, you need to implement the code for the configured GDPR message to be displayed.

Result Screen

This is the screen observed during testing.

On the left is the message displayed when the app is launched.
On the right, a Change GDPR Settings button has been added to the settings to allow changing GDPR settings. Clicking this button displays a message screen similar to the left image.

Result Screen

Implementation

1. Modify proguard-rules.pro

Following the guidance from React Native Google Mobile Ads - European User Consent, add the following content to the android/app/proguard-rules.pro file.

-keep class com.google.android.gms.internal.consent_sdk.** { *; }

2. Delaying app measurement

Set the delay_app_measurement_init value to true in the react-native-google-mobile-ads configuration in the app.json file.

{
  "react-native-google-mobile-ads": {
    "android_app_id": "ca-app-pub-xxxxxxxx~xxxxxxxx",
    "ios_app_id": "ca-app-pub-xxxxxxxx~xxxxxxxx",
    "delay_app_measurement_init": true
  }
}

3. Code

I've created a common function file, gdprAdsConsent.ts, for loading GDPR consent messages.

  • gdprAdsConsent.ts
// https://docs.page/invertase/react-native-google-mobile-ads/european-user-consent
import mobileAds, {AdsConsent, AdsConsentDebugGeography} from 'react-native-google-mobile-ads';

const debugParams = {
  debugGeography: AdsConsentDebugGeography.EEA,
  testDeviceIdentifiers: ['Your-Test-Device-Hashed-Id'],
}

// Initialize AdsMob
function initializeMobileAdsSdk() {
  mobileAds()
    .initialize()
    .then(adapterStatuses => {
      console.log("mobileAds initialize:", adapterStatuses);
    });
}

// Check consent status
async function checkIsNotAgreement() {
  const {
    activelyScanDeviceCharacteristicsForIdentification,
    applyMarketResearchToGenerateAudienceInsights,
    createAPersonalisedAdsProfile,
    createAPersonalisedContentProfile,
    developAndImproveProducts,
    measureAdPerformance,
    measureContentPerformance,
    selectBasicAds,
    selectPersonalisedAds,
    selectPersonalisedContent,
    storeAndAccessInformationOnDevice,
    usePreciseGeolocationData,
  } = await AdsConsent.getUserChoices();

  return (
    applyMarketResearchToGenerateAudienceInsights === false ||
    createAPersonalisedAdsProfile === false ||
    createAPersonalisedContentProfile === false ||
    developAndImproveProducts === false ||
    measureAdPerformance === false ||
    measureContentPerformance === false ||
    selectBasicAds === false ||
    selectPersonalisedAds === false ||
    selectPersonalisedContent === false ||
    storeAndAccessInformationOnDevice === false ||
    usePreciseGeolocationData === false
  )
}

async function requestConsent() {
  return await AdsConsent.requestInfoUpdate();
  //return await AdsConsent.requestInfoUpdate(debugParams); // Add debugParams during testing
}

// Load GDPR consent form and check consent status
export async function loadGdprAdsConsent(
) {
  try{
    //await AdsConsent.reset(); // Uncomment for testing
    const data = await requestConsent();

    if(data.isConsentFormAvailable){
      const resultForm = await AdsConsent.loadAndShowConsentFormIfRequired();

      // If the user has already consented or refused consent, check the consent status and request consent if needed
      if(data.status === 'OBTAINED'){
        const isNotAgreement = await checkIsNotAgreement();

        if(isNotAgreement) {
          await AdsConsent.showForm();
        }
      }

      if(resultForm.canRequestAds === true) {
        initializeMobileAdsSdk()
      }
    } else {
      initializeMobileAdsSdk()
    }
  }
  catch(error) {
    console.log('loadGdprAdsConsent > error: ', error);
    initializeMobileAdsSdk();
  }
}

// Check if Consent is Available (EEA region)
export async function checkIsConsentAvailable() {
  try{
    const data = await requestConsent();
    //console.log('checkConsentAvailable > data: ', data);

    return data.isConsentFormAvailable;
  }catch (error) {
    console.log('checkConsentAvailable > error: ', error);
    return false;
  }
}

// Show consent form on button click or screen navigation, and check consent status afterward
export async function showAdsConsentForm(
  consentCallback?: () => void,
  notConsentCallback?: () => void,
) {
  try{
    const data = await requestConsent();

    if(data.isConsentFormAvailable){
      const isNotAgreement = await checkIsNotAgreement();

      if(isNotAgreement) {
        await AdsConsent.showForm();

        // Re-check consent status
        const isNotAgreementResult = await checkIsNotAgreement();

        if(isNotAgreementResult) {
          // Handle non-consent
          notConsentCallback && notConsentCallback();
        } else {
          // Handle consent
          consentCallback && consentCallback();
        }
      } else {
        // Handle consent
        consentCallback && consentCallback();
      }
    }
    else {
      // Handle case where no consent form is available
      consentCallback && consentCallback();
    }
  }catch (error) {
    console.log('showAdsConsentForm > error: ', error);
  }
}

// Show privacy options form in settings
export async function showPrivacyOptionsForm() {
  try{
    const data = await requestConsent();

    if(data.isConsentFormAvailable){
      await AdsConsent.showPrivacyOptionsForm()
    }
  }catch (error) {
    console.log('showPrivacyOptionsForm > error: ', error);
  }
}
  • The verification of consent status (checkIsNotAgreement) can be conditional based on the specific consents required.
    • Since it may not be clear under which conditions the ads are displayed correctly, I chose to examine the property set to true when the "Consent" button is clicked and used that to determine the consent status.

4. Test Configuration

Change 'Your-Test-Device-Hashed-ID'

  1. Enter the following command in the terminal to check the device's hashed ID.
  • Look for the log entry: Use RequestConfiguration.Builder().setTestDeviceIds(Arrays.asList("Your-Test-Device-Hashed-ID")) to get test ads on this device.
adb shell "logcat -d | grep Ads"
  1. Apply test configuration using debugParams
  • โ— Use this only for testing purposes, and remove debugParams during deployment. โ—
async function requestConsent() {
  //return await AdsConsent.requestInfoUpdate();
  return await AdsConsent.requestInfoUpdate(debugParams); // Add debugParams during testing
}

5. Implementation

  1. Load GDPR on app launch and request consent if not given.
// App.tsx
import React, { useEffect } from 'react';
import AppNavigator from './navigation/AppNavigator';
import { loadGdprAdsConsent } from './api/gdprAdsConsent';

export default function App() {

  useEffect(() => {
    loadGdprAdsConsent().then(() => {
      console.log("loadGdprAdsConsent completed");
    }).catch((err) => {
      console.log("loadGdprAdsConsent err: ", err);
    });
  }, []);

  return (
    <AppNavigator />
  );
}
  1. If consent is not given, request consent when the user clicks the "New Group" or "Create Group" button.
  // When the user clicks the "New Group" button
  const onClickNewGroup = () => {
    showAdsConsentForm(() => {
      GlobalModal.showPopup(<GroupCreateModal refresh={refresh} />, t('create_group'))
    }).then(() => {})
  }
  // When navigating to the settings
  const goToSetting = () => {
    showAdsConsentForm(() => {
      navigation.navigate('Setting');
    }).then(() => {})
  }
  1. Added a button in the settings screen to allow the user to change their consent status.
export const SettingScreen = () => {
  const [isConsentAvailable, setIsConsentAvailable] = useState(false);

  useEffect(() => {
    checkIsConsentAvailable().then((isAvailable) => {
      setIsConsentAvailable(isAvailable);
    })
  }, []);

  return (
    <View style={globalStyles.container}>
      {isConsentAvailable &&
      <TouchableOpacity style={styles.listContainer} onPress={() => showPrivacyOptionsForm()}>
        <AppText style={styles.listTitle}>Change GDPR Settings</AppText>
      </TouchableOpacity>}
    </View>
  );
}
  • If the GDPR message is not loading:

    • Check on the Google AdMob website whether GDPR has been published for the app.
    • Confirm that the 'Your-Test-Device-Hashed-ID' is correct.
  • When testing in test mode โ†” non-test mode, and the previous settings persist, causing testing issues:

    • Test mode: Use AdsConsent.requestInfoUpdate(debugParams);, uncomment await AdsConsent.reset(); to reset the settings, then comment it back after resetting.
    • Non-test mode: Use AdsConsent.requestInfoUpdate();, uncomment await AdsConsent.reset(); to reset the settings, then comment it back after resetting.
  • If the loading behavior is incorrect (e.g., loading when it shouldn't or not loading when it should):

    • The selected information is stored in the Local Storage. Deleting the app's storage and relaunching resolved the issue.
    • If the above step doesn't work, as a last resort, uninstall the app and reinstall it to ensure proper functionality.

References