On this page14 sections
Lecture 009 — Rewarded Interstitial Ads
| Category | 3 — Native & Rewarded Ads |
| Lecture | 009 of 010 |
| Estimated video length | 18 min |
| Target | Flutter 3.44 · Dart 3.12 · google_mobile_ads 9 |
| Level | Beginner |
| Source code | GitHub — urraan_admob_final |
A rewarded interstitial looks like an interstitial but also gives a reward. Google asks you to warn the user first, so we add a small dialog before the ad.
Contents
Prerequisites · Connection · Objectives · Lesson · Code Examples · Common Mistakes · Best Practices · Mini Challenge · Practice Questions · Answers · Takeaways
Prerequisites
Lectures 005 and 008.
Connection With Previous Lectures
It combines the previous two: a full-screen ad that starts at a natural pause, with a reward.
Learning Objectives
By the end of this lecture you will be able to:
- Load and show a
RewardedInterstitialAd - Show an intro dialog before the ad
- Grant the reward and keep
AdStatein sync
Lesson
1. The Helper
// lib/admob/rewarded_interstitial_ad_helper.dart
import 'package:flutter/foundation.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';
import 'admob_manager.dart';
class RewardedInterstitialAdHelper {
bool isAdLoaded = false;
RewardedInterstitialAd? _ad;
void loadRewardedInterstitialAd({
required VoidCallback onAdShowFullScreen,
required VoidCallback onAdDismissed,
}) {
RewardedInterstitialAd.load(
adUnitId: AdmobManager.rewardedInterstitialId,
request: const AdRequest(),
rewardedInterstitialAdLoadCallback: RewardedInterstitialAdLoadCallback(
onAdLoaded: (ad) {
isAdLoaded = true;
_ad = ad;
ad.fullScreenContentCallback = FullScreenContentCallback(
onAdShowedFullScreenContent: (_) => onAdShowFullScreen(),
onAdDismissedFullScreenContent: (ad) {
ad.dispose();
isAdLoaded = false;
_ad = null;
onAdDismissed();
loadRewardedInterstitialAd(
onAdShowFullScreen: onAdShowFullScreen,
onAdDismissed: onAdDismissed,
);
},
onAdFailedToShowFullScreenContent: (ad, error) {
ad.dispose();
isAdLoaded = false;
_ad = null;
},
);
},
onAdFailedToLoad: (error) {
isAdLoaded = false;
debugPrint('Rewarded interstitial failed: ${error.code} ${error.message}');
},
),
);
}
bool showAdIfAvailable(void Function(RewardItem reward) onEarnedReward) {
if (_ad == null || !isAdLoaded) return false;
_ad!.show(onUserEarnedReward: (_, reward) => onEarnedReward(reward));
return true;
}
void dispose() => _ad?.dispose();
}It mirrors the rewarded helper: load, callbacks, showAdIfAvailable, reload.
2. The Screen With a Dialog
// lib/screen_rewarded_interstitial_ads.dart
import 'package:flutter/material.dart';
import 'admob/ad_state.dart';
import 'admob/rewarded_interstitial_ad_helper.dart';
class ScreenRewardedInterstitialAds extends StatefulWidget {
const ScreenRewardedInterstitialAds({super.key});
@override
State<ScreenRewardedInterstitialAds> createState() =>
_ScreenRewardedInterstitialAdsState();
}
class _ScreenRewardedInterstitialAdsState
extends State<ScreenRewardedInterstitialAds> {
final adHelper = RewardedInterstitialAdHelper();
int _coins = 0;
@override
void initState() {
super.initState();
adHelper.loadRewardedInterstitialAd(
onAdShowFullScreen: () => AdState().disableAppOpenAds(),
onAdDismissed: () => AdState().enableAppOpenAdsAfterCooldown(),
);
}
@override
void dispose() {
adHelper.dispose();
super.dispose();
}
void _showDialogThenAd() {
showDialog(
context: context,
builder: (dialogContext) => AlertDialog(
title: const Text('Information'),
content: const Text(
'To access this feature you need to watch an ad first.',
),
actions: [
TextButton(
onPressed: () => Navigator.of(dialogContext).pop(),
child: const Text('Cancel'),
),
TextButton(
onPressed: () {
Navigator.of(dialogContext).pop();
_watchAd();
},
child: const Text('Watch ad'),
),
],
),
);
}
void _watchAd() {
final shown = adHelper.showAdIfAvailable((reward) {
setState(() => _coins += reward.amount.toInt());
});
if (!shown) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Ad is not ready yet, try again')),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Rewarded Interstitial Ads')),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('Coins: $_coins', style: const TextStyle(fontSize: 24)),
const SizedBox(height: 20),
ElevatedButton(
onPressed: _showDialogThenAd,
child: const Text('Download'),
),
],
),
),
);
}
}- The dialog tells the user an ad is coming and why. Cancel closes it; Watch ad closes it then shows the ad.
- We close the dialog with its own
dialogContext, then call_watchAd— never use the same context for both. AdStatedisables app-open ads while the ad shows.
3. Rewarded vs Rewarded Interstitial
| Rewarded | Rewarded interstitial | |
|---|---|---|
| Starts when | User taps a button | At a natural pause (you trigger it) |
| Warning | Button text | Required intro screen |
Code Examples
Example 1 — Dialog Wording
"Watch a short ad to unlock this download?" with Cancel and Watch ad is clear. A dialog that says only "Continue" is not.
Common Beginner Mistakes
1. No intro — Google requires the user to be told first.
2. Using the dialog's context after it closes — use the screen's context for the snackbar.
3. Rewarding on dismiss.
Best Practices
- Always show an intro before a rewarded interstitial
- Offer a clear Cancel
- Keep reward logic in the callback
Mini Challenge
Skip the dialog if the user has already watched one ad today (store a DateTime).
Practice Questions
- Why a dialog first?
- Which callback grants the reward?
- Which format needs a button tap to start?
- Why two contexts?
Practice Answers
- Users must know an ad will play and why.
onUserEarnedReward.- Rewarded.
- The dialog's context is invalid after it closes.
Key Takeaways
- Intro dialog is required
- Reward in the callback
- Same helper pattern as rewarded