- Published on
- ยท15 min read
Applying AdMob GDPR on 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 Smart Table Clock app.
For the implementation on React Native (Android), you can refer to Applying AdMob GDPR in React Native (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.
GDPR Message Setup
Go to Google AdMob > GDPR > Click on [Create Message]
button.
Configure the settings according to your app. Here are my settings.
- 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 I verified while in test mode.
On the left is the message displayed when the app is launched.
On the right, I added a button in the settings to change the GDPR settings. Clicking this button displays the message screen on the left.
Implementation
1. Install with Gradle
Follow the instructions from Google AdMob, UMP. Add the following content to the dependencies
section of your app/build.gradle
file:
dependencies {
// ... existing content ...
implementation 'com.google.android.ump:user-messaging-platform:2.1.0'
}
Then sync your project.
2. App Measurement Delay
Follow the instructions in Google AdMob, UMP > GDPR IAB Support > Delay app measurement. Add the following content to your AndroidManifest.xml
file:
<manifest>
<application>
<!-- existing content -->
<!-- Delay app measurement until MobileAds.initialize() is called. -->
<meta-data
android:name="com.google.android.gms.ads.DELAY_APP_MEASUREMENT_INIT"
android:value="true"/>
</application>
</manifest>
3. Code
I fetched and applied the
GoogleMobileAdsConsentManager.kt
file from googleads-mobile-android-examples.The API provided by Google does not have a part that informs about user selection (consent / non-consent). It only has the
OBTAINED
status for the selection. For this reason, I added code to theGoogleMobileAdsConsentManager.kt
file to check whether there is consent or not.- I referred to stackoverflow - How to implement UMP SDK correctly for eu consent?.
- Android AdMob consent with UMP โ Personalized or Non-Personalized Ads in EEA provides a well-explained guide.
The content of the
GoogleMobileAdsConsentManager.kt
file is as follows:
package your_package_name
import android.app.Activity
import android.content.Context
import com.google.android.ump.ConsentDebugSettings
import com.google.android.ump.ConsentForm.OnConsentFormDismissedListener
import com.google.android.ump.ConsentInformation
import com.google.android.ump.ConsentRequestParameters
import com.google.android.ump.FormError
import com.google.android.ump.UserMessagingPlatform
/**
* The Google Mobile Ads SDK provides the User Messaging Platform (Google's IAB Certified consent
* management platform) as one solution to capture consent for users in GDPR impacted countries.
* This is an example and you can choose another consent management platform to capture consent.
*/
class GoogleMobileAdsConsentManager private constructor(context: Context) {
private val consentInformation: ConsentInformation =
UserMessagingPlatform.getConsentInformation(context)
/** Interface definition for a callback to be invoked when consent gathering is complete. */
fun interface OnConsentGatheringCompleteListener {
fun consentGatheringComplete(error: FormError?)
}
/** Helper variable to determine if the app can request ads. */
val canRequestAds: Boolean
get() = consentInformation.canRequestAds()
/** Helper variable to determine if the privacy options form is required. */
val isPrivacyOptionsRequired: Boolean
get() =
consentInformation.privacyOptionsRequirementStatus ==
ConsentInformation.PrivacyOptionsRequirementStatus.REQUIRED
// Code added for consent and non-consent checks [[
// https://itnext.io/android-admob-consent-with-ump-personalized-or-non-personalized-ads-in-eea-3592e192ec90
// https://stackoverflow.com/questions/65351543/how-to-implement-ump-sdk-correctly-for-eu-consent/68310602#68310602
fun isGDPR(context: Context): Boolean {
val prefs = context.getSharedPreferences(context.packageName + "_preferences", Context.MODE_PRIVATE)
val gdpr = prefs.getInt("IABTCF_gdprApplies", 0)
return gdpr == 1
}
fun canShowAds(context: Context): Boolean {
//val prefs = PreferenceManager.getDefaultSharedPreferences(context)
// Using PreferenceManager as it is deprecated
val prefs = context.getSharedPreferences(context.packageName + "_preferences", Context.MODE_PRIVATE)
val purposeConsent = prefs.getString("IABTCF_PurposeConsents", "") ?: ""
val vendorConsent = prefs.getString("IABTCF_VendorConsents", "") ?: ""
val vendorLI = prefs.getString("IABTCF_VendorLegitimateInterests", "") ?: ""
val purposeLI = prefs.getString("IABTCF_PurposeLegitimateInterests", "") ?: ""
val googleId = 755
val hasGoogleVendorConsent = hasAttribute(vendorConsent, googleId)
val hasGoogleVendorLI = hasAttribute(vendorLI, googleId)
// Personalized Ads: listOf(1, 3, 4)
return hasConsentFor(listOf(1), purposeConsent, hasGoogleVendorConsent)
&& hasConsentOrLegitimateInterestFor(
listOf(2,7,9,10),
purposeConsent,
purposeLI,
hasGoogleVendorConsent,
hasGoogleVendorLI
)
}
// Check if a binary string has a "1" at position "index" (1-based)
private fun hasAttribute(input: String, index: Int): Boolean {
return input.length >= index && input[index-1] == '1'
}
// Check if consent is given for a list of purposes
private fun hasConsentFor(purposes: List<Int>, purposeConsent: String, hasVendorConsent: Boolean): Boolean {
return purposes.all { p -> hasAttribute(purposeConsent, p)} && hasVendorConsent
}
// Check if a vendor either has consent or legitimate interest for a list of purposes
private fun hasConsentOrLegitimateInterestFor(purposes: List<Int>, purposeConsent: String, purposeLI: String, hasVendorConsent: Boolean, hasVendorLI: Boolean): Boolean {
return purposes.all { p ->
(hasAttribute(purposeLI, p) && hasVendorLI) ||
(hasAttribute(purposeConsent, p) && hasVendorConsent)
}
}
// Code added for consent and non-consent checks ]]
/**
* Helper method to call the UMP SDK methods to request consent information and load/show a
* consent form if necessary.
*/
fun gatherConsent(
activity: Activity,
onConsentGatheringCompleteListener: OnConsentGatheringCompleteListener
) {
// For testing purposes, you can force a DebugGeography of EEA or NOT_EEA.
val debugSettings =
ConsentDebugSettings.Builder(activity)
.setDebugGeography(ConsentDebugSettings.DebugGeography.DEBUG_GEOGRAPHY_EEA) // Forcibly setting to the EEA region
// Check your logcat output for the hashed device ID e.g.
// "Use new ConsentDebugSettings.Builder().addTestDeviceHashedId("ABCDEF012345")" to use
// the debug functionality.
.addTestDeviceHashedId("Your-test-device-hash-id")
.build()
val params = ConsentRequestParameters
.Builder()
//.setConsentDebugSettings(debugSettings) // Use only during testing
.build()
// consentInformation.reset() // Reconfigure consent status; use only during testing
// Requesting an update to consent information should be called on every app launch.
consentInformation.requestConsentInfoUpdate(
activity,
params,
{
UserMessagingPlatform.loadAndShowConsentFormIfRequired(activity) { formError ->
// Consent has been gathered.
onConsentGatheringCompleteListener.consentGatheringComplete(formError)
}
},
{ requestConsentError ->
onConsentGatheringCompleteListener.consentGatheringComplete(requestConsentError)
}
)
}
/** Helper method to call the UMP SDK method to show the privacy options form. */
fun showPrivacyOptionsForm(
activity: Activity,
onConsentFormDismissedListener: OnConsentFormDismissedListener
) {
UserMessagingPlatform.showPrivacyOptionsForm(activity, onConsentFormDismissedListener)
}
companion object {
@Volatile private var instance: GoogleMobileAdsConsentManager? = null
fun getInstance(context: Context) =
instance
?: synchronized(this) {
instance ?: GoogleMobileAdsConsentManager(context).also { instance = it }
}
}
}
4. Test Setup
Change 'Your-test-device-hash-id'
- In
GoogleMobileAdsConsentManager.kt
, replace 'Your-test-device-hash-id' with your device value.
- Your device's hashed ID: Copy and paste the value printed in the log, which looks like
Use new ConsentDebugSettings.Builder().addTestDeviceHashedId("Your device's hashed ID")
.
- Uncomment the
setConsentDebugSettings
section to apply the test settings.
- โ Use this only during testing, and be sure to comment it out before deploying. โ
val params = ConsentRequestParameters
.Builder()
.setConsentDebugSettings(debugSettings) // Uncomment for testing, use only during testing
.build()
5. Implementation
The app handles GDPR and ad loading in the
ClockFragment
.For countries with GDPR, it loads the consent/non-consent pop-up when GDPR is initially loaded. After that, when the settings button is clicked, it checks the consent status and reloads the consent pop-up if not consented.
Following recommendations, I allowed users to change their consent status in the settings.
ClockFragment.kt
onCreate
>checkAdMobGDPRConsent
: Load GDPR.onViewCreated
>binding.btnSetting.setOnClickListener
: Check GDPR consent status when the settings icon is clicked.
class ClockFragment : Fragment() {
private var interstitialAd: InterstitialAd? = null // AdMob Interstitial Ad related
private val isMobileAdsInitializeCalled = AtomicBoolean(false)
private lateinit var googleMobileAdsConsentManager: GoogleMobileAdsConsentManager
private var TAG = "ClockFragment"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Load GDPR
checkAdMobGDPRConsent()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// When the settings icon is clicked, navigate to the settings screen
binding.btnSetting.setOnClickListener {
val isGDPR = googleMobileAdsConsentManager.isGDPR(requireContext())
val canShowAds = googleMobileAdsConsentManager.canShowAds(requireContext())
// Check consent status if GDPR confirmation is required
if(isGDPR && !canShowAds){
googleMobileAdsConsentManager.showPrivacyOptionsForm(requireActivity()) { formError ->
if (formError != null) {
// Move to the settings screen in case of an error
Toast.makeText(requireContext(), formError.message, Toast.LENGTH_SHORT).show()
showSetting()
} else {
// Recheck the consent status
val result = googleMobileAdsConsentManager.canShowAds(requireContext())
// Move to the settings screen if consent is obtained
if(result){
showSetting()
}
}
}
} else{
showSetting()
}
}
// Omitted
}
private fun showSetting() {
// AdMob Interstitial Ad related, show ad when conditions are met
if (SETTING_CLICKED_COUNT % ADMOB_CHECK_COUNT == 0) {
showInterstitial()
}
else{
findNavController().navigate(R.id.action_clockFragment_to_previewAndSettingFragment)
SETTING_CLICKED_COUNT++
}
}
private fun checkAdMobGDPRConsent(){
googleMobileAdsConsentManager = GoogleMobileAdsConsentManager.getInstance(requireContext())
googleMobileAdsConsentManager.gatherConsent(
requireActivity(),
) { error ->
Log.i(
TAG,
"Consent gathering result: ${error?.message}, googleMobileAdsConsentManager.canRequestAds: ${googleMobileAdsConsentManager.canRequestAds}"
)
if (error != null) {
// Consent not obtained in current session.
Log.d(TAG, "${error.errorCode}: ${error.message}")
initializeMobileAdsSdk()
} else {
if (googleMobileAdsConsentManager.canRequestAds) {
initializeMobileAdsSdk()
}
}
}
// This sample attempts to load ads using consent obtained in the previous session.
if (googleMobileAdsConsentManager.canRequestAds) {
initializeMobileAdsSdk()
}
}
/**
* AdMob Interstitial Ad related
*/
private fun initializeMobileAdsSdk() {
if (isMobileAdsInitializeCalled.getAndSet(true)) {
return
}
val appContext = activity?.applicationContext ?: return
MobileAds.initialize(appContext) {}
loadInterstitialAd(appContext)
}
// Load the ad
private fun loadInterstitialAd(context: Context){
val adRequest = AdRequest.Builder().build()
InterstitialAd.load(context, getString(R.string.interstitial_ad_unit_id), adRequest,
object : InterstitialAdLoadCallback() {
// Omitted, interstitialAd = ad Or interstitialAd = null
})
}
private fun showInterstitial() {
// Omitted, display the ad
}
// Omitted
}
If GDPR message loading is not happening:
- Check on the Google AdMob site if GDPR has been published for the app.
- Verify if 'Your-test-device-hash-id' is correct.
When switching between test mode and non-test mode for testing and previous settings persist, preventing successful testing:
- Test mode: Uncomment
.setConsentDebugSettings(debugSettings)
, uncommentconsentInformation.reset()
to reset the settings, then comment it again. - Non-test mode: Comment out
//.setConsentDebugSettings(null)
, uncommentconsentInformation.reset()
to reset the settings, then comment it again.
- Test mode: Uncomment
If it loads when it shouldn't or doesn't load when it should:
- The selected information is stored in Local Storage. Therefore, clearing the storage for the app and relaunching resolved the issue.
- If that doesn't work, as a last resort, uninstall the app and then reinstall it, which resolved the issue.
SettingFragment.kt
Added a button in the settings to allow changing GDPR settings.
When the button is clicked, a GDPR message appears, allowing the user to choose consent/non-consent.
class SettingFragment : Fragment() {
private lateinit var googleMobileAdsConsentManager: GoogleMobileAdsConsentManager
// Omitted
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Omitted
googleMobileAdsConsentManager = GoogleMobileAdsConsentManager.getInstance(requireContext())
binding.btnGdprSetting.isVisible = googleMobileAdsConsentManager.isPrivacyOptionsRequired
binding.btnGdprSetting.setOnClickListener {
googleMobileAdsConsentManager.showPrivacyOptionsForm(requireActivity()) { formError ->
if (formError != null) {
Toast.makeText(requireContext(), formError.message, Toast.LENGTH_SHORT).show()
}
}
}
}
// Omitted
}