diff --git a/samples/admob/rewarded_example/lib/consent_manager.dart b/samples/admob/rewarded_example/lib/consent_manager.dart new file mode 100644 index 000000000..59c23ec76 --- /dev/null +++ b/samples/admob/rewarded_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/rewarded_example/lib/countdown_timer.dart b/samples/admob/rewarded_example/lib/countdown_timer.dart index 1dfeb4de6..6d378b7c3 100644 --- a/samples/admob/rewarded_example/lib/countdown_timer.dart +++ b/samples/admob/rewarded_example/lib/countdown_timer.dart @@ -4,27 +4,45 @@ import 'package:flutter/material.dart'; enum CountdownState { notStarted, active, + paused, ended, } /// A simple class that keeps track of a decrementing timer. class CountdownTimer extends ChangeNotifier { - final _countdownTime = 10; + final _countdownTime = 5; late var timeLeft = _countdownTime; var _countdownState = CountdownState.notStarted; + Timer? _timer; bool get isComplete => _countdownState == CountdownState.ended; void start() { timeLeft = _countdownTime; - _resumeTimer(); + _startTimer(); _countdownState = CountdownState.active; notifyListeners(); } - void _resumeTimer() { - Timer.periodic(const Duration(seconds: 1), (timer) { + void resume() { + if (_countdownState != CountdownState.paused) { + return; + } + _startTimer(); + _countdownState = CountdownState.active; + } + + void pause() { + if (_countdownState != CountdownState.active) { + return; + } + _timer?.cancel(); + _countdownState = CountdownState.paused; + } + + void _startTimer() { + _timer = Timer.periodic(const Duration(seconds: 1), (timer) { timeLeft--; if (timeLeft == 0) { diff --git a/samples/admob/rewarded_example/lib/main.dart b/samples/admob/rewarded_example/lib/main.dart index 76a362a76..edd1319e4 100644 --- a/samples/admob/rewarded_example/lib/main.dart +++ b/samples/admob/rewarded_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: RewardedExample(), )); @@ -20,8 +21,14 @@ class RewardedExample extends StatefulWidget { } class RewardedExampleState extends State { + static const privacySettingsText = 'Privacy Settings'; + + final _consentManager = ConsentManager(); final CountdownTimer _countdownTimer = CountdownTimer(); var _showWatchVideoButton = false; + var _gamePaused = false; + var _gameOver = false; + var _isMobileAdsInitializeCalled = false; var _coins = 0; RewardedAd? _rewardedAd; @@ -33,20 +40,55 @@ class RewardedExampleState extends State { void initState() { super.initState(); + _consentManager.gatherConsent((consentGatheringError) { + if (consentGatheringError != null) { + // Consent not obtained in current session. + debugPrint( + "${consentGatheringError.errorCode}: ${consentGatheringError.message}"); + } + + // Kick off the first play of the "game". + _startNewGame(); + + // Attempt to initialize the Mobile Ads SDK. + _initializeMobileAdsSDK(); + }); + + // This sample attempts to load ads using consent obtained in the previous session. + _initializeMobileAdsSDK(); + + // Show the "Watch video" button when the timer reaches zero. _countdownTimer.addListener(() => setState(() { if (_countdownTimer.isComplete) { + _gameOver = true; _showWatchVideoButton = true; _coins += 1; } else { _showWatchVideoButton = false; } })); - _startNewGame(); } void _startNewGame() { - _loadAd(); _countdownTimer.start(); + _gameOver = false; + _gamePaused = false; + } + + void _pauseGame() { + if (_gameOver || _gamePaused) { + return; + } + _countdownTimer.pause(); + _gamePaused = true; + } + + void _resumeGame() { + if (_gameOver || !_gamePaused) { + return; + } + _countdownTimer.resume(); + _gamePaused = false; } @override @@ -55,8 +97,10 @@ class RewardedExampleState extends State { title: 'Rewarded Example', home: Scaffold( appBar: AppBar( - title: const Text('Rewarded Example'), - ), + title: const Text('Rewarded Example'), + actions: _isMobileAdsInitializeCalled + ? _privacySettingsAppBarAction() + : null), body: Stack( children: [ const Align( @@ -82,6 +126,7 @@ class RewardedExampleState extends State { child: TextButton( onPressed: () { _startNewGame(); + _loadAd(); }, child: const Text('Play Again'), ), @@ -116,8 +161,48 @@ class RewardedExampleState extends State { ); } + 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) { + _pauseGame(); + _consentManager.showPrivacyOptionsForm((formError) { + if (formError != null) { + debugPrint( + "${formError.errorCode}: ${formError.message}"); + } + _resumeGame(); + }); + } + }, + itemBuilder: (BuildContext context) => + >[ + const PopupMenuItem( + value: privacySettingsText, + child: Text(privacySettingsText)) + ], + )); + }) + ]; + } + /// Loads a rewarded 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; + } + RewardedAd.load( adUnitId: _adUnitId, request: const AdRequest(), @@ -146,6 +231,26 @@ class RewardedExampleState extends State { })); } + /// 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(() { + _isMobileAdsInitializeCalled = true; + }); + + // Initialize the Mobile Ads SDK. + MobileAds.instance.initialize(); + // Load an ad. + _loadAd(); + } + } + @override void dispose() { _rewardedAd?.dispose(); diff --git a/samples/admob/rewarded_example/pubspec.yaml b/samples/admob/rewarded_example/pubspec.yaml index 3b165cf24..6c3f4ad44 100644 --- a/samples/admob/rewarded_example/pubspec.yaml +++ b/samples/admob/rewarded_example/pubspec.yaml @@ -9,7 +9,7 @@ environment: dependencies: flutter: sdk: flutter - google_mobile_ads: ^5.0.0 + google_mobile_ads: ^5.1.0 dev_dependencies: flutter_test: