diff --git a/samples/admob/app_open_example/lib/app_open_ad_manager.dart b/samples/admob/app_open_example/lib/app_open_ad_manager.dart index 02a868c15..810f1aab2 100644 --- a/samples/admob/app_open_example/lib/app_open_ad_manager.dart +++ b/samples/admob/app_open_example/lib/app_open_ad_manager.dart @@ -13,6 +13,7 @@ // limitations under the License. // ignore_for_file: public_member_api_docs +import 'package:app_open_example/consent_manager.dart'; import 'package:flutter/material.dart'; import 'package:google_mobile_ads/google_mobile_ads.dart'; import 'dart:io' show Platform; @@ -33,7 +34,14 @@ class AppOpenAdManager { : 'ca-app-pub-3940256099942544/5575463023'; /// Load an [AppOpenAd]. - void loadAd() { + void loadAd() async { + // Only load an ad if the Mobile Ads SDK has gathered consent aligned with + // the app's configured messages. + var canRequestAds = await ConsentManager.instance.canRequestAds(); + if (!canRequestAds) { + return; + } + AppOpenAd.load( adUnitId: adUnitId, request: const AdRequest(), diff --git a/samples/admob/app_open_example/lib/consent_manager.dart b/samples/admob/app_open_example/lib/consent_manager.dart new file mode 100644 index 000000000..f429d449b --- /dev/null +++ b/samples/admob/app_open_example/lib/consent_manager.dart @@ -0,0 +1,54 @@ +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 { + ConsentManager._(); + static final ConsentManager instance = 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); + } +} \ No newline at end of file diff --git a/samples/admob/app_open_example/lib/main.dart b/samples/admob/app_open_example/lib/main.dart index 3ee3cdba6..7c84674dc 100644 --- a/samples/admob/app_open_example/lib/main.dart +++ b/samples/admob/app_open_example/lib/main.dart @@ -19,9 +19,10 @@ import 'package:google_mobile_ads/google_mobile_ads.dart'; import 'app_lifecycle_reactor.dart'; import 'app_open_ad_manager.dart'; +import 'consent_manager.dart'; + void main() { WidgetsFlutterBinding.ensureInitialized(); - MobileAds.instance.initialize(); runApp(const MainApp()); } @@ -48,16 +49,34 @@ class HomePage extends StatefulWidget { /// Example home page for an app open ad. class _HomePageState extends State { + static const privacySettingsText = 'Privacy Settings'; + + final _appOpenAdManager = AppOpenAdManager(); + var _isMobileAdsInitializeCalled = false; int _counter = 0; late AppLifecycleReactor _appLifecycleReactor; @override void initState() { super.initState(); - AppOpenAdManager appOpenAdManager = AppOpenAdManager()..loadAd(); + _appLifecycleReactor = - AppLifecycleReactor(appOpenAdManager: appOpenAdManager); + AppLifecycleReactor(appOpenAdManager: _appOpenAdManager); _appLifecycleReactor.listenToAppStateChanges(); + + ConsentManager.instance.gatherConsent((consentGatheringError) { + if (consentGatheringError != null) { + // Consent not obtained in current session. + debugPrint( + "${consentGatheringError.errorCode}: ${consentGatheringError.message}"); + } + + // Attempt to initialize the Mobile Ads SDK. + _initializeMobileAdsSDK(); + }); + + // This sample attempts to load ads using consent obtained in the previous session. + _initializeMobileAdsSDK(); } void _incrementCounter() { @@ -71,6 +90,8 @@ class _HomePageState extends State { return Scaffold( appBar: AppBar( title: const Text('App Open Demo Home Page'), + actions: + _isMobileAdsInitializeCalled ? _privacySettingsAppBarAction() : [], ), body: Center( child: Column( @@ -93,4 +114,56 @@ class _HomePageState extends State { ), // This trailing comma makes auto-formatting nicer for build methods. ); } + + List _privacySettingsAppBarAction() { + return [ + // Regenerate the options menu to include a privacy setting. + FutureBuilder( + future: ConsentManager.instance.isPrivacyOptionsRequired(), + builder: (context, snapshot) { + final bool visibility = snapshot.data ?? false; + return Visibility( + visible: visibility, + child: PopupMenuButton( + onSelected: (String result) { + if (result == privacySettingsText) { + ConsentManager.instance + .showPrivacyOptionsForm((formError) { + if (formError != null) { + debugPrint( + "${formError.errorCode}: ${formError.message}"); + } + }); + } + }, + itemBuilder: (BuildContext context) => + >[ + const PopupMenuItem( + value: privacySettingsText, + child: Text(privacySettingsText)) + ], + )); + }) + ]; + } + + /// Initialize the Mobile Ads SDK if the SDK has gathered consent aligned with + /// the app's configured messages. + void _initializeMobileAdsSDK() async { + if (_isMobileAdsInitializeCalled) { + return; + } + + var canRequestAds = await ConsentManager.instance.canRequestAds(); + if (canRequestAds) { + setState(() { + _isMobileAdsInitializeCalled = true; + }); + + // Initialize the Mobile Ads SDK. + MobileAds.instance.initialize(); + // Load an ad. + _appOpenAdManager.loadAd(); + } + } } diff --git a/samples/admob/app_open_example/pubspec.yaml b/samples/admob/app_open_example/pubspec.yaml index 64bd93999..a445f579b 100644 --- a/samples/admob/app_open_example/pubspec.yaml +++ b/samples/admob/app_open_example/pubspec.yaml @@ -7,9 +7,13 @@ environment: sdk: '>=3.2.0 <4.0.0' dependencies: + google_mobile_ads: + git: + url: https://github.com/googleads/googleads-mobile-flutter/ + path: ./packages/google_mobile_ads + flutter: sdk: flutter - google_mobile_ads: ^5.0.0 dev_dependencies: flutter_test: