diff --git a/samples/admob/native_template_example/android/build.gradle b/samples/admob/native_template_example/android/build.gradle index 58a8c74b1..713d7f6e6 100644 --- a/samples/admob/native_template_example/android/build.gradle +++ b/samples/admob/native_template_example/android/build.gradle @@ -26,6 +26,6 @@ subprojects { project.evaluationDependsOn(':app') } -task clean(type: Delete) { +tasks.register("clean", Delete) { delete rootProject.buildDir } diff --git a/samples/admob/native_template_example/lib/consent_manager.dart b/samples/admob/native_template_example/lib/consent_manager.dart new file mode 100644 index 000000000..f8d8c7912 --- /dev/null +++ b/samples/admob/native_template_example/lib/consent_manager.dart @@ -0,0 +1,51 @@ +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); + } +} diff --git a/samples/admob/native_template_example/lib/main.dart b/samples/admob/native_template_example/lib/main.dart index e12f0b9ca..4fac7c35c 100644 --- a/samples/admob/native_template_example/lib/main.dart +++ b/samples/admob/native_template_example/lib/main.dart @@ -3,9 +3,10 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:google_mobile_ads/google_mobile_ads.dart'; +import 'consent_manager.dart'; + void main() { WidgetsFlutterBinding.ensureInitialized(); - MobileAds.instance.initialize(); runApp(const MaterialApp( home: NativeExample(), )); @@ -20,9 +21,13 @@ class NativeExample extends StatefulWidget { } class NativeExampleState extends State { + static const privacySettingsText = 'Privacy Settings'; + + final _consentManager = ConsentManager(); + var _isMobileAdsInitializeCalled = false; NativeAd? _nativeAd; bool _nativeAdIsLoaded = false; - String? _versionString; + // final double _adAspectRatioSmall = (91 / 355); final double _adAspectRatioMedium = (370 / 355); @@ -34,8 +39,19 @@ class NativeExampleState extends State { void initState() { super.initState(); - _loadAd(); - _loadVersionString(); + _consentManager.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(); } @override @@ -44,8 +60,10 @@ class NativeExampleState extends State { title: 'Native Example', home: Scaffold( appBar: AppBar( - title: const Text('Native Example'), - ), + title: const Text('Native Example'), + actions: _isMobileAdsInitializeCalled + ? _privacySettingsAppBarAction() + : null), body: SizedBox( height: MediaQuery.of(context).size.height, width: MediaQuery.of(context).size.width, @@ -67,14 +85,57 @@ class NativeExampleState extends State { ), TextButton( onPressed: _loadAd, child: const Text("Refresh Ad")), - if (_versionString != null) Text(_versionString!) + FutureBuilder( + future: MobileAds.instance.getVersionString(), + builder: (context, snapshot) { + var versionString = snapshot.data ?? ""; + return Text(versionString); + }) ], ), ))); } + List _privacySettingsAppBarAction() { + return [ + // Regenerate the options menu to include a privacy setting. + FutureBuilder( + future: _consentManager.isPrivacyOptionsRequired(), + builder: (context, snapshot) { + final bool visibility = snapshot.data ?? false; + return Visibility( + visible: visibility, + child: PopupMenuButton( + onSelected: (String result) { + if (result == privacySettingsText) { + _consentManager.showPrivacyOptionsForm((formError) { + if (formError != null) { + debugPrint( + "${formError.errorCode}: ${formError.message}"); + } + }); + } + }, + itemBuilder: (BuildContext context) => + >[ + const PopupMenuItem( + value: privacySettingsText, + child: Text(privacySettingsText)) + ], + )); + }) + ]; + } + /// Loads a native ad. - 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.canRequestAds(); + if (!canRequestAds) { + return; + } + setState(() { _nativeAdIsLoaded = false; }); @@ -124,12 +185,24 @@ class NativeExampleState extends State { ..load(); } - void _loadVersionString() { - MobileAds.instance.getVersionString().then((value) { + /// 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.canRequestAds(); + if (canRequestAds) { setState(() { - _versionString = value; + _isMobileAdsInitializeCalled = true; }); - }); + + // Initialize the Mobile Ads SDK. + MobileAds.instance.initialize(); + // Load an ad. + _loadAd(); + } } @override diff --git a/samples/admob/native_template_example/pubspec.yaml b/samples/admob/native_template_example/pubspec.yaml index eb392d650..107d45ac7 100644 --- a/samples/admob/native_template_example/pubspec.yaml +++ b/samples/admob/native_template_example/pubspec.yaml @@ -30,7 +30,7 @@ environment: dependencies: flutter: sdk: flutter - google_mobile_ads: ^5.0.0 + google_mobile_ads: ^5.1.0 # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons.