integrating-google-ads — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited integrating-google-ads (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
google_mobile_ads v7.x)Integrate Google Mobile Ads securely and efficiently. Prevent memory leaks by strictly managing AdWidget lifecycles and ensure high fill-rates by pre-loading full-screen ads before they are needed.
Before calling any Dart code, the native manifests must be explicitly configured with the Google Mobile Ads application ID.
Android (`android/app/src/main/AndroidManifest.xml`) Add the <meta-data> tag immediately inside the <application> tag.
<manifest>
<application>
<!-- Use TEST ID for development: ca-app-pub-3940256099942544~3347511713 -->
<meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="YOUR_ANDROID_APP_ID"/>
</application>
</manifest>iOS (`ios/Runner/Info.plist`) Add GADApplicationIdentifier.
<key>GADApplicationIdentifier</key>
<!-- Use TEST ID for development: ca-app-pub-3940256099942544~1458002511 -->
<string>YOUR_IOS_APP_ID</string>Flutter (`main.dart`) Initialize the SDK eagerly before runApp.
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await MobileAds.instance.initialize();
runApp(const MyApp());
}Always use Google's predefined test ad units during development and testing. Using your own live production IDs on test devices will lead to immediate AdSense account suspension.
Android Test IDs:
ca-app-pub-3940256099942544/6300978111ca-app-pub-3940256099942544/1033173712ca-app-pub-3940256099942544/5224354917ca-app-pub-3940256099942544/2247696110iOS Test IDs:
ca-app-pub-3940256099942544/2934735716ca-app-pub-3940256099942544/4411468910ca-app-pub-3940256099942544/1712485313ca-app-pub-3940256099942544/3986624511Banner ads are rendered organically within the Flutter widget tree using an AdWidget.
Implementation Pattern:
BannerAd instance in the State.initState._isLoaded flag to render the AdWidget only when ready..dispose() in dispose.class BannerAdWidget extends StatefulWidget {
@override
_BannerAdWidgetState createState() => _BannerAdWidgetState();
}
class _BannerAdWidgetState extends State<BannerAdWidget> {
BannerAd? _bannerAd;
bool _isLoaded = false;
final String adUnitId = Platform.isAndroid
? 'ca-app-pub-3940256099942544/6300978111'
: 'ca-app-pub-3940256099942544/2934735716';
@override
void initState() {
super.initState();
_loadAd();
}
void _loadAd() {
_bannerAd = BannerAd(
adUnitId: adUnitId,
request: const AdRequest(),
size: AdSize.banner, // e.g. 320x50
listener: BannerAdListener(
onAdLoaded: (ad) => setState(() => _isLoaded = true),
onAdFailedToLoad: (ad, err) {
ad.dispose(); // CRITICAL
},
),
)..load();
}
@override
void dispose() {
_bannerAd?.dispose(); // CRITICAL
super.dispose();
}
@override
Widget build(BuildContext context) {
if (_bannerAd != null && _isLoaded) {
return SafeArea(
child: SizedBox(
width: _bannerAd!.size.width.toDouble(),
height: _bannerAd!.size.height.toDouble(),
child: AdWidget(ad: _bannerAd!),
),
);
}
return const SizedBox.shrink();
}
}Interstitials are full-screen overlays. They MUST be pre-loaded before the user triggers the logic that displays them.
Implementation Pattern:
class InterstitialManager {
InterstitialAd? _interstitialAd;
// Utilize the test IDs defined above during development
final String adUnitId = Platform.isAndroid
? 'ca-app-pub-3940256099942544/1033173712'
: 'ca-app-pub-3940256099942544/4411468910';
/// Call this when the user ENTERS the screen/level.
void loadAd() {
InterstitialAd.load(
adUnitId: adUnitId,
request: const AdRequest(),
adLoadCallback: InterstitialAdLoadCallback(
onAdLoaded: (ad) {
ad.fullScreenContentCallback = FullScreenContentCallback(
onAdDismissedFullScreenContent: (ad) {
ad.dispose(); // CRITICAL
loadAd(); // Pre-load the next one immediately
},
onAdFailedToShowFullScreenContent: (ad, error) {
ad.dispose(); // CRITICAL
},
);
_interstitialAd = ad;
},
onAdFailedToLoad: (err) => print('Failed to load: $err'),
),
);
}
/// Call this when the user leaves the screen/finishes a level.
void showAd(VoidCallback onDone) {
if (_interstitialAd != null) {
// Define a one-time wrapper if you need logic strictly after dismissal
_interstitialAd!.fullScreenContentCallback?.onAdDismissedFullScreenContent = (ad) {
ad.dispose();
loadAd();
onDone(); // Resume app logic
};
_interstitialAd!.show();
_interstitialAd = null; // Prevent showing twice
} else {
onDone(); // Fallback if ad wasn't loaded
}
}
}Used for giving users points/lives in exchange for watching a video. Similar lifecycle to Interstitials but requires secure reward handling.
Implementation Pattern:
RewardedAd? _rewardedAd;
void _loadRewardedAd() {
RewardedAd.load(
adUnitId: Platform.isAndroid
? 'ca-app-pub-3940256099942544/5224354917'
: 'ca-app-pub-3940256099942544/1712485313',
request: const AdRequest(),
rewardedAdLoadCallback: RewardedAdLoadCallback(
onAdLoaded: (ad) {
ad.fullScreenContentCallback = FullScreenContentCallback(
onAdDismissedFullScreenContent: (ad) {
ad.dispose();
_loadRewardedAd(); // Reload
},
);
_rewardedAd = ad;
},
onAdFailedToLoad: (err) {},
),
);
}
void _showRewardedAd() {
if (_rewardedAd != null) {
_rewardedAd!.show(
// CRITICAL: Handle the reward logic here
onUserEarnedReward: (AdWithoutView ad, RewardItem reward) {
print('User earned: ${reward.amount} ${reward.type}');
// Grant virtual currency
},
);
_rewardedAd = null;
}
}Native ads match the look and feel of your app. Google now provides NativeTemplateStyle to avoid writing complex custom Java/Swift factories unless absolutely necessary.
NativeAd? _nativeAd;
bool _nativeAdIsLoaded = false;
void _loadNativeAd() {
_nativeAd = NativeAd(
// Utilize the test IDs defined above during development
adUnitId: Platform.isAndroid
? 'ca-app-pub-3940256099942544/2247696110'
: 'ca-app-pub-3940256099942544/3986624511',
request: const AdRequest(),
// Use predefined templates for zero native code
nativeTemplateStyle: NativeTemplateStyle(
templateType: TemplateType.medium,
mainBackgroundColor: Colors.white,
cornerRadius: 10.0,
callToActionTextStyle: NativeTemplateTextStyle(
textColor: Colors.white,
backgroundColor: Colors.blue,
style: NativeTemplateFontStyle.bold,
size: 16.0,
),
),
listener: NativeAdListener(
onAdLoaded: (ad) => setState(() => _nativeAdIsLoaded = true),
onAdFailedToLoad: (ad, error) {
ad.dispose();
},
),
)..load();
}
// In build method:
if (_nativeAdIsLoaded && _nativeAd != null)
ConstrainedBox(
constraints: const BoxConstraints(
minWidth: 320, // minimum recommended width
minHeight: 320, // minimum recommended height
maxWidth: 400,
maxHeight: 400,
),
child: AdWidget(ad: _nativeAd!),
).dispose() on any ad instance inside onAdFailedToLoad, onAdDismissedFullScreenContent, or the parent StatefulWidget's dispose() method. Failure to do so degrades app performance significantly.Ad.load() prior to awaiting MobileAds.instance.initialize().~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.