Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding UMP SDK integration to Rewarded Interstitial sample #1069

Merged
merged 7 commits into from
May 9, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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<bool> canRequestAds() async {
return await ConsentInformation.instance.canRequestAds();
}

/// Helper variable to determine if the privacy options form is required.
Future<bool> 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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
enum CountdownState {
notStarted,
active,
paused,
ended,
}

Expand All @@ -13,19 +14,36 @@ class CountdownTimer extends ChangeNotifier {
late var timeLeft = _countdownTime;
var _countdownState = CountdownState.notStarted;
bool get isComplete => _countdownState == CountdownState.ended;
Timer? _timer;

CountdownTimer(this._countdownTime);

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) {
Expand Down
140 changes: 125 additions & 15 deletions samples/admob/rewarded_interstitial_example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import 'package:flutter/material.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';
import 'package:rewarded_interstitial_example/ad_dialog.dart';

import 'consent_manager.dart';

void main() {
WidgetsFlutterBinding.ensureInitialized();
MobileAds.instance.initialize();
runApp(const MaterialApp(
home: RewardedInterstitialExample(),
));
Expand All @@ -23,8 +24,14 @@ class RewardedInterstitialExample extends StatefulWidget {

class RewardedInterstitialExampleState
extends State<RewardedInterstitialExample> {
final CountdownTimer _countdownTimer = CountdownTimer(10);
static const privacySettingsText = 'Privacy Settings';

final _consentManager = ConsentManager();
final CountdownTimer _countdownTimer = CountdownTimer(5);
var _coins = 0;
var _gamePaused = false;
var _gameOver = false;
var _isMobileAdsInitializeCalled = false;
RewardedInterstitialAd? _rewardedInterstitialAd;

final String _adUnitId = Platform.isAndroid
Expand All @@ -35,31 +42,69 @@ class RewardedInterstitialExampleState
void initState() {
super.initState();

_countdownTimer.addListener(() => setState(() {
_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 an alert dialog when the timer reaches zero.
_countdownTimer.addListener(() =>
setState(() {
if (_countdownTimer.isComplete) {
showDialog(
context: context,
builder: (context) => AdDialog(showAd: () {
builder: (context) =>
AdDialog(showAd: () {
_gameOver = true;
_showAdCallback();
}));
_coins += 1;
}
}));
_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;
}

void _showAdCallback() {
_rewardedInterstitialAd?.show(
onUserEarnedReward: (AdWithoutView view, RewardItem rewardItem) {
// ignore: avoid_print
print('Reward amount: ${rewardItem.amount}');
setState(() => _coins += rewardItem.amount.toInt());
});
// ignore: avoid_print
print('Reward amount: ${rewardItem.amount}');
setState(() => _coins += rewardItem.amount.toInt());
});
}

@override
Expand All @@ -68,7 +113,10 @@ class RewardedInterstitialExampleState
title: 'Rewarded Interstitial Example',
home: Scaffold(
appBar: AppBar(
title: const Text('Rewarded Interstitial Example'),
title: const Text('Rewarded Interstitial Example'),
actions: _isMobileAdsInitializeCalled
? _privacySettingsAppBarAction()
: null
),
body: Stack(
children: [
Expand All @@ -79,7 +127,7 @@ class RewardedInterstitialExampleState
child: Text(
'The Impossible Game',
style:
TextStyle(fontSize: 25, fontWeight: FontWeight.bold),
TextStyle(fontSize: 25, fontWeight: FontWeight.bold),
),
)),
Align(
Expand All @@ -95,6 +143,7 @@ class RewardedInterstitialExampleState
child: TextButton(
onPressed: () {
_startNewGame();
_loadAd();
},
child: const Text('Play Again'),
),
Expand All @@ -112,15 +161,56 @@ class RewardedInterstitialExampleState
);
}

List<Widget> _privacySettingsAppBarAction() {
return <Widget>[
// 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<String>(
onSelected: (String result) {
if (result == privacySettingsText) {
_pauseGame();
_consentManager.showPrivacyOptionsForm((formError) {
if (formError != null) {
debugPrint(
"${formError.errorCode}: ${formError.message}");
}
_resumeGame();
});
}
},
itemBuilder: (BuildContext context) =>
<PopupMenuEntry<String>>[
const PopupMenuItem<String>(
value: privacySettingsText,
child: Text(privacySettingsText))
],
));
})
];
}


/// Loads a rewarded interstitial 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;
}

RewardedInterstitialAd.load(
adUnitId: _adUnitId,
request: const AdRequest(),
rewardedInterstitialAdLoadCallback:
RewardedInterstitialAdLoadCallback(onAdLoaded: (ad) {
RewardedInterstitialAdLoadCallback(onAdLoaded: (ad) {
ad.fullScreenContentCallback = FullScreenContentCallback(
// Called when the ad showed the full screen content.
// Called when the ad showed the full screen content.
onAdShowedFullScreenContent: (ad) {},
// Called when an impression occurs on the ad.
onAdImpression: (ad) {},
Expand All @@ -143,6 +233,26 @@ class RewardedInterstitialExampleState
}));
}

/// 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() {
_rewardedInterstitialAd?.dispose();
Expand Down
6 changes: 5 additions & 1 deletion samples/admob/rewarded_interstitial_example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down