googleads / googleads-mobile-flutter

A Flutter plugin for the Google Mobile Ads SDK
Apache License 2.0
343 stars 285 forks source link

Ads are shown even when user does not consent (GDPR Compliance Issue) #1147

Closed vacating closed 3 months ago

vacating commented 3 months ago

Plugin Version

google_mobile_ads: ^5.1.0

[REQUIRED] Step 2: Describe the problem

I'm encountering an issue with GDPR consent handling using the google_mobile_ads package version ^5.1.0 in my Flutter app. Despite following the official guide and using the example provided, the consent status is not being stored correctly, causing ads to not load or load as expected : @malandr2

Steps to Reproduce

Documentation and Example Used:

Official Guide Example Code:

`import 'dart:async'; import 'package:google_mobile_ads/google_mobile_ads.dart';

typedef OnConsentGatheringCompleteListener = void Function(FormError? error);

class ConsentManager { Future canRequestAds() async { return await ConsentInformation.instance.canRequestAds(); }

Future isPrivacyOptionsRequired() async { return await ConsentInformation.instance .getPrivacyOptionsRequirementStatus() == PrivacyOptionsRequirementStatus.required; }

void resetConsent() { ConsentInformation.instance.reset(); }

void gatherConsent( OnConsentGatheringCompleteListener onConsentGatheringCompleteListener) { ConsentDebugSettings debugSettings = ConsentDebugSettings( debugGeography: DebugGeography.debugGeographyEea, // Force to EU for testing ); ConsentRequestParameters params = ConsentRequestParameters(consentDebugSettings: debugSettings);

ConsentInformation.instance.requestConsentInfoUpdate(params, () async {
  ConsentForm.loadAndShowConsentFormIfRequired((loadAndShowError) {
    onConsentGatheringCompleteListener(loadAndShowError);
  });
}, (FormError formError) {
  onConsentGatheringCompleteListener(formError);
});

}

void showPrivacyOptionsForm( OnConsentFormDismissedListener onConsentFormDismissedListener) { ConsentForm.showPrivacyOptionsForm(onConsentFormDismissedListener); } } `

`import 'dart:io'; import 'package:flutter/material.dart'; import 'package:google_mobile_ads/google_mobile_ads.dart'; import 'package:grow_todo/consent/ConsentManager.dart'; import 'app_bar_item.dart';

class BannerExample extends StatefulWidget { const BannerExample({super.key});

@override BannerExampleState createState() => BannerExampleState(); }

class BannerExampleState extends State { final _consentManager = ConsentManager(); var _isMobileAdsInitializeCalled = false; var _isPrivacyOptionsRequired = false; BannerAd? _bannerAd; bool _isLoaded = false; Orientation? _currentOrientation;

final String _adUnitId = Platform.isAndroid ? 'ca-app-pub-3940256099942544/9214589741' : 'ca-app-pub-3940256099942544/2435281174';

@override void initState() { super.initState();

// Reset consent status for testing purposes
_consentManager.resetConsent();

_consentManager.gatherConsent((consentGatheringError) {
  if (consentGatheringError != null) {
    debugPrint("${consentGatheringError.errorCode}: ${consentGatheringError.message}");
  }

  _getIsPrivacyOptionsRequired();
  _initializeMobileAdsSDK();
});

_initializeMobileAdsSDK();

}

@override Widget build(BuildContext context) { return MaterialApp( title: 'Banner Example', home: Scaffold( appBar: AppBar( title: const Text('Banner Example'), actions: _appBarActions()), body: OrientationBuilder( builder: (context, orientation) { if (_currentOrientation != orientation) { _isLoaded = false; _loadAd(); _currentOrientation = orientation; } return Stack( children: [ if (_bannerAd != null && _isLoaded) Align( alignment: Alignment.bottomCenter, child: SafeArea( child: SizedBox( width: _bannerAd!.size.width.toDouble(), height: _bannerAd!.size.height.toDouble(), child: AdWidget(ad: _bannerAd!), ), ), ) ], ); }, ))); }

List _appBarActions() { var array = [AppBarItem(AppBarItem.adInpsectorText, 0)];

if (_isPrivacyOptionsRequired) {
  array.add(AppBarItem(AppBarItem.privacySettingsText, 1));
}

return <Widget>[
  PopupMenuButton<AppBarItem>(
      itemBuilder: (context) => array
          .map((item) => PopupMenuItem<AppBarItem>(
                value: item,
                child: Text(
                  item.label,
                ),
              ))
          .toList(),
      onSelected: (item) {
        switch (item.value) {
          case 0:
            MobileAds.instance.openAdInspector((error) {
              // Error will be non-null if ad inspector closed due to an error.
            });
          case 1:
            _consentManager.showPrivacyOptionsForm((formError) {
              if (formError != null) {
                debugPrint("${formError.errorCode}: ${formError.message}");
              }
            });
        }
      })
];

}

void _loadAd() async { var canRequestAds = await _consentManager.canRequestAds(); if (!canRequestAds) { return; }

if (!mounted) {
  return;
}

final size = await AdSize.getCurrentOrientationAnchoredAdaptiveBannerAdSize(
    MediaQuery.sizeOf(context).width.truncate());

if (size == null) {
  return;
}

BannerAd(
  adUnitId: _adUnitId,
  request: const AdRequest(),
  size: size,
  listener: BannerAdListener(
    onAdLoaded: (ad) {
      setState(() {
        _bannerAd = ad as BannerAd;
        _isLoaded = true;
      });
    },
    onAdFailedToLoad: (ad, err) {
      ad.dispose();
    },
    onAdOpened: (Ad ad) {},
    onAdClosed: (Ad ad) {},
    onAdImpression: (Ad ad) {},
  ),
).load();

}

void _getIsPrivacyOptionsRequired() async { if (await _consentManager.isPrivacyOptionsRequired()) { setState(() { _isPrivacyOptionsRequired = true; }); } }

void _initializeMobileAdsSDK() async { if (_isMobileAdsInitializeCalled) { return; }

if (await _consentManager.canRequestAds()) {
  _isMobileAdsInitializeCalled = true;
  MobileAds.instance.initialize();
  _loadAd();
}

}

@override void dispose() { _bannerAd?.dispose(); super.dispose(); } } `

Expected results: The consent status should be stored correctly, allowing ads to be loaded and displayed based on the user's consent. altho i dont consent it laod and show the banner ad:

ps: no error on flutter analyze

Actual results:

it shows the ads altho user didnt consent

/UserMessagingPlatform(25181): Stored info not exists: IABTCF_TCString D/UserMessagingPlatform(25181): Stored info not exists: IABGPP_HDR_GppString D/UserMessagingPlatform(25181): Stored info not exists: IABGPP_GppSID D/UserMessagingPlatform(25181): Stored info not exists: IABUSPrivacy_String D/UserMessagingPlatform(25181): Action[write]: {"IABTCF_CmpSdkID":300,"IABTCF_gdprApplies":1} D/UserMessagingPlatform(25181): Writing to storage: [IABTCF_CmpSdkID] 300 D/UserMessagingPlatform(25181): Writing to storage: [IABTCF_gdprApplies] 1 D/UserMessagingPlatform(25181): Action[start_transparency_status_updates]: {} D/UserMessagingPlatform(25181): Receive consent action: consent://consent/?action=configure_app_assets D/UserMessagingPlatform(25181): Action[configure_app_assets]: {} D/UserMessagingPlatform(25181): Stored info not exists: IDFA_freqCapNumViews D/UserMessagingPlatform(25181): Stored info not exists: IABTCF_TCString D/UserMessagingPlatform(25181): Stored info not exists: IABTCF_AddtlConsent ``` ``` ``` ``` [√] Flutter (Channel stable, 3.22.3, on Microsoft Windows [Version 10.0.19045.4717], locale en-US) • Flutter version 3.22.3 on channel stable at C:\Users\YourUsername\dev\src\flutter • Framework revision b0850beeb2 (11 days ago), 2024-07-16 21:43:41 -0700 • Engine revision 235db911ba • Dart version 3.4.4 • DevTools version 2.34.3 [√] Windows Version (Installed version of Windows is version 10 or higher) [√] Android toolchain - develop for Android devices (Android SDK version 34.0.0) • Android SDK at C:\Users\YourUsername\AppData\Local\Android\sdk • Platform android-34, build-tools 34.0.0 • Java binary at: C:\Program Files\Android\Android Studio\jbr\bin\java • Java version OpenJDK Runtime Environment (build 17.0.11+0--11852314) • All Android licenses accepted. [√] Chrome - develop for the web • Chrome at C:\Program Files\Google\Chrome\Application\chrome.exe [!] Visual Studio - develop Windows apps (Visual Studio Community 2022 17.8.5) • Visual Studio at C:\Program Files\Microsoft Visual Studio\2022\Community • Visual Studio Community 2022 version 17.8.34511.84 X Visual Studio is missing necessary components. Please re-run the Visual Studio installer for the "Desktop development with C++" workload, and include these components: MSVC v142 - VS 2019 C++ x64/x86 build tools - If there are multiple build tool versions available, install the latest C++ CMake tools for Windows Windows 10 SDK [√] Android Studio (version 2024.1) • Android Studio at C:\Program Files\Android\Android Studio • Flutter plugin can be installed from: https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: https://plugins.jetbrains.com/plugin/6351-dart • Java version OpenJDK Runtime Environment (build 17.0.11+0--11852314) [√] VS Code (version 1.91.1) • VS Code at C:\Users\YourUsername\AppData\Local\Programs\Microsoft VS Code • Flutter extension version 3.92.0 [√] Connected device (4 available) • Android SDK built for x86 (mobile) • emulator-5554 • android-x86 • Android 10 (API 29) (emulator) • Windows (desktop) • windows • windows-x64 • Microsoft Windows [Version 10.0.19045.4717] • Chrome (web) • chrome • web-javascript • Google Chrome 126.0.6478.185 • Edge (web) • edge • web-javascript • Microsoft Edge 127.0.2651.74 [√] Network resources • All expected network resources are available. ! Doctor found issues in 1 category. ``` ```
malandr2 commented 3 months ago

Hi @vacating, users who select "DO NOT CONSENT" are eligible to receive Limited ads. This is the expected behavior

vacating commented 3 months ago

Hello @malandr2 thanks for the response.

i followed the link that you referred to Limited ads, and i turned off the Programmatic limited ads in my admobe account.

i run my code and i denied the consent i chose " do not consent " the banner ad is still showing,

i am using test banner id ID for banner ad And in the AndroidManifest.xml am using my real admobe account ID for the andriod.

these are the logs i get after i dont consent to ads but the ads still showing

_D/UserMessagingPlatform( 8653): Writing to storage: [IABTCF_CmpSdkID] 300 D/UserMessagingPlatform( 8653): Writing to storage: [IABTCF_CmpSdkVersion] 2 D/UserMessagingPlatform( 8653): Writing to storage: [IABTCF_PolicyVersion] 5 D/UserMessagingPlatform( 8653): Writing to storage: [IABTCF_gdprApplies] 1 D/UserMessagingPlatform( 8653): Writing to storage: [IABTCF_PurposeOneTreatment] 0 D/UserMessagingPlatform( 8653): Writing to storage: [IABTCF_UseNonStandardStacks] 0 D/UserMessagingPlatform( 8653): Writing to storage: [IABTCFVendorConsents] 0

and an error

W/Ads (12681): Not retrying to fetch app settings

can you please guide where the issue would be what other aspect of the admobe i should check or in code.

malandr2 commented 3 months ago

Hi @vacating, users are still eligible to receive ads when they click "DO NOT CONSENT". See our Frequently asked questions and specifically, How can I check if a user consented?

You can also check the Privacy Signals section after integrating Ad Inspector, the samples have it included.

vacating commented 3 months ago

hay @malandr2 hello,

could u please see this code , its just 2 file code: the issue is when i withdraw my consent while the banner ad is active it wont disappear unless i go out of the page and come back then its disappear.

**` import 'dart:async';

import 'package:google_mobile_ads/google_mobile_ads.dart';

typedef OnConsentGatheringCompleteListener = void Function(FormError? error);

/// 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 ConsentManager { /// Helper variable to determine if the app can request ads. Future canRequestAds() async { return await ConsentInformation.instance.canRequestAds(); }

/// Helper variable to determine if the privacy options form is required. Future isPrivacyOptionsRequired() async { return await ConsentInformation.instance .getPrivacyOptionsRequirementStatus() == PrivacyOptionsRequirementStatus.required; }

/// Helper method to call the Mobile Ads SDK to request consent information /// and load/show a consent form if necessary. void gatherConsent( OnConsentGatheringCompleteListener onConsentGatheringCompleteListener) { // For testing purposes, you can force a DebugGeography of Eea or NotEea. ConsentDebugSettings debugSettings = ConsentDebugSettings( // debugGeography: DebugGeography.debugGeographyEea, ); ConsentRequestParameters params = ConsentRequestParameters(consentDebugSettings: debugSettings);

// Requesting an update to consent information should be called on every app launch.
ConsentInformation.instance.requestConsentInfoUpdate(params, () async {
  ConsentForm.loadAndShowConsentFormIfRequired((loadAndShowError) {
    // Consent has been gathered.
    onConsentGatheringCompleteListener(loadAndShowError);
  });
}, (FormError formError) {
  onConsentGatheringCompleteListener(formError);
});

}

/// Helper method to call the Mobile Ads SDK method to show the privacy options form. void showPrivacyOptionsForm( OnConsentFormDismissedListener onConsentFormDismissedListener) { ConsentForm.showPrivacyOptionsForm(onConsentFormDismissedListener); } }`**

TEST PAGE FILE:

**`import 'package:flutter/material.dart'; import 'package:google_mobile_ads/google_mobile_ads.dart'; import 'package:grow_todo/consent/consent_manager.dart';

class TestPage extends StatefulWidget { @override _TestPageState createState() => _TestPageState(); }

class _TestPageState extends State { BannerAd? _bannerAd; bool _isBannerAdReady = false;

@override void initState() { super.initState(); _initializeAds(); }

void _initializeAds() { ConsentManager().gatherConsent((error) { if (error == null) { _loadBannerAd(); } else { print('Consent error: ${error.message}'); } }); }

void _loadBannerAd() { _bannerAd = BannerAd( adUnitId: 'ca-app-pub-3940256099942544/6300978111', request: AdRequest(), size: AdSize.banner, listener: BannerAdListener( onAdLoaded: (ad) { setState(() { _isBannerAdReady = true; }); // Register ad for potential disposal ConsentManager.registerAd(ad); }, onAdFailedToLoad: (ad, error) { print('Ad failed to load: ${error.message}'); _isBannerAdReady = false; ad.dispose(); }, ), )..load(); }

void _showConsentMessage() { ConsentManager().showPrivacyOptionsForm((FormError? error) { if (error != null) { print('Privacy options form error: ${error.message}'); } else { print('Privacy options form dismissed.'); _initializeAds(); } }); }

@override void dispose() { _bannerAd?.dispose(); super.dispose(); }

@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Test Page')), body: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ if (_isBannerAdReady) Container( height: _bannerAd?.size.height.toDouble(), width: _bannerAd?.size.width.toDouble(), child: AdWidget(ad: _bannerAd!), ), Spacer(), Padding( padding: const EdgeInsets.all(16.0), child: ElevatedButton( onPressed: _showConsentMessage, child: Text('Load Consent Message'), ), ), ], ), ); } } `** if you could say how to remove the current running ads in widget tree after the new ads are stopped , it would be billions of thanks to you

vacating commented 3 months ago

@malandr2 the issue was that i had to dispose the ads in lifecycle diposead() if it fails

thank you for all ur help and assessment god bless , and i give u the "budge of billion thanks" in what u helped.

cheers have good one.