.agents/skills/implement-admob/SKILL.md
Implements Google AdMob ads (banner, native, interstitial) in Flutter following the project architecture. Use whenever adding or modifying ad-related files, integrating the AdMob SDK, adding monetization via ads, creating banner or native ad widgets, managing interstitial ad lifecycle, or centralizing ad unit IDs. Covers AdConfig centralized IDs, AdService SDK init, InterstitialAdService lifecycle, AdBannerWidget, AdNativeWidget, DI registration, and anti-patterns. Activate even when the user says 'add ads to my app', 'show a banner ad', 'monetize with AdMob', 'integrate Google ads', 'show a native ad', or 'display an interstitial' without explicitly mentioning AdConfig or AdService.
npx skillsauth add andrelucassvt/CleanMacForDevsWeb implement-admobInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
AppInitializer.adUnitId, carrega e descarta sozinho.adUnitId e templateType.load() no initState() e show() com Future.delayed após o conteúdo estar visível.AdService e InterstitialAdService → registerLazySingleton.BuildContext para os services de anúncio.MobileAds.instance.initialize() diretamente fora de AdService.dependencies:
google_mobile_ads: ^5.x.x
lib/
├── common/
│ ├── services/
│ │ └── ads/
│ │ ├── ad_config.dart # IDs de anúncios por plataforma
│ │ ├── ad_service.dart # Inicialização do SDK
│ │ └── interstitial_ad_service.dart # Gerenciamento de intersticiais
│ └── widgets/
│ ├── ad_banner_widget.dart # Widget de banner
│ └── ad_native_widget.dart # Widget nativo
class AppInitializer {
static Future<void> initialize(AppFlavor flavor) async {
WidgetsFlutterBinding.ensureInitialized();
await AppInjector.setupDependencies(flavor: flavor);
// Inicializa o SDK de anúncios (sempre após setupDependencies)
await AppInjector.inject.get<AdService>().initialize();
}
}
import 'dart:io';
class AdConfig {
const AdConfig._();
static String get nativeBanner1 {
if (Platform.isAndroid) return 'ca-app-pub-XXXXXXXXXXXXXXXX/XXXXXXXXXX';
if (Platform.isIOS) return 'ca-app-pub-XXXXXXXXXXXXXXXX/XXXXXXXXXX';
throw UnsupportedError('Plataforma não suportada para anúncios');
}
static String get banner2 {
if (Platform.isAndroid) return 'ca-app-pub-XXXXXXXXXXXXXXXX/XXXXXXXXXX';
if (Platform.isIOS) return 'ca-app-pub-XXXXXXXXXXXXXXXX/XXXXXXXXXX';
throw UnsupportedError('Plataforma não suportada para anúncios');
}
static String get interstitial {
if (Platform.isAndroid) return 'ca-app-pub-XXXXXXXXXXXXXXXX/XXXXXXXXXX';
if (Platform.isIOS) return 'ca-app-pub-XXXXXXXXXXXXXXXX/XXXXXXXXXX';
throw UnsupportedError('Plataforma não suportada para anúncios');
}
}
Regras:
Platform.isAndroid / Platform.isIOS com IDs separadosUnsupportedError para plataformas não suportadasconst AdConfig._() — não instanciávelAdConfigif com return separados — não if/elseimport 'dart:developer';
import 'package:google_mobile_ads/google_mobile_ads.dart';
class AdService {
bool _initialized = false;
Future<void> initialize() async {
if (_initialized) return;
await MobileAds.instance.initialize();
_initialized = true;
log('AdService: Google Mobile Ads initialized');
}
}
Regras:
if (_initialized) return para evitar inicialização duplalog() do dart:developer — nunca print()registerLazySingletonimport 'dart:developer';
import 'package:base_app/common/services/ads/ad_config.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';
class InterstitialAdService {
InterstitialAd? _interstitialAd;
bool _isLoading = false;
bool get isReady => _interstitialAd != null;
Future<void> load() async {
if (_interstitialAd != null || _isLoading) return;
_isLoading = true;
await InterstitialAd.load(
adUnitId: AdConfig.interstitial,
request: const AdRequest(),
adLoadCallback: InterstitialAdLoadCallback(
onAdLoaded: (ad) {
_interstitialAd = ad;
_isLoading = false;
log('InterstitialAdService: ad loaded');
ad.fullScreenContentCallback = FullScreenContentCallback(
onAdDismissedFullScreenContent: (ad) {
ad.dispose();
_interstitialAd = null;
load(); // Pré-carrega o próximo automaticamente
},
onAdFailedToShowFullScreenContent: (ad, error) {
log('InterstitialAdService: failed to show — $error');
ad.dispose();
_interstitialAd = null;
},
);
},
onAdFailedToLoad: (error) {
_isLoading = false;
log('InterstitialAdService: failed to load — $error');
},
),
);
}
void show() {
if (_interstitialAd == null) {
log('InterstitialAdService: ad not ready, loading...');
load();
return;
}
_interstitialAd!.show();
}
void dispose() {
_interstitialAd?.dispose();
_interstitialAd = null;
}
}
Regras:
load()onAdDismissedFullScreenContentshow() seguro: tenta recarregar se não estiver prontoregisterLazySingletonBuildContext para este serviçoimport 'dart:developer';
import 'package:flutter/material.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';
class AdBannerWidget extends StatefulWidget {
const AdBannerWidget({
required this.adUnitId,
this.adSize = AdSize.banner,
super.key,
});
final String adUnitId;
final AdSize adSize;
@override
State<AdBannerWidget> createState() => _AdBannerWidgetState();
}
class _AdBannerWidgetState extends State<AdBannerWidget> {
BannerAd? _bannerAd;
bool _isLoaded = false;
@override
void initState() {
super.initState();
_loadAd();
}
void _loadAd() {
_bannerAd = BannerAd(
adUnitId: widget.adUnitId,
size: widget.adSize,
request: const AdRequest(),
listener: BannerAdListener(
onAdLoaded: (ad) {
if (mounted) setState(() => _isLoaded = true);
},
onAdFailedToLoad: (ad, error) {
log('AdBannerWidget: failed to load — $error');
ad.dispose();
_bannerAd = null;
},
),
)..load();
}
@override
void dispose() {
_bannerAd?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
if (!_isLoaded || _bannerAd == null) return const SizedBox.shrink();
return SizedBox(
width: widget.adSize.width.toDouble(),
height: widget.adSize.height.toDouble(),
child: AdWidget(ad: _bannerAd!),
);
}
}
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';
class AdNativeWidget extends StatefulWidget {
const AdNativeWidget({
required this.adUnitId,
this.templateType = TemplateType.medium,
super.key,
});
final String adUnitId;
final TemplateType templateType;
@override
State<AdNativeWidget> createState() => _AdNativeWidgetState();
}
class _AdNativeWidgetState extends State<AdNativeWidget> {
NativeAd? _nativeAd;
bool _isLoaded = false;
@override
void initState() {
super.initState();
_loadAd();
}
void _loadAd() {
_nativeAd = NativeAd(
adUnitId: widget.adUnitId,
listener: NativeAdListener(
onAdLoaded: (ad) {
if (mounted) setState(() => _isLoaded = true);
},
onAdFailedToLoad: (ad, error) {
log('AdNativeWidget: failed to load — $error');
ad.dispose();
_nativeAd = null;
},
),
request: const AdRequest(),
nativeTemplateStyle: NativeTemplateStyle(templateType: widget.templateType),
)..load();
}
@override
void dispose() {
_nativeAd?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
if (!_isLoaded || _nativeAd == null) return const SizedBox.shrink();
return ConstrainedBox(
constraints: const BoxConstraints(minWidth: 320, minHeight: 90, maxHeight: 340),
child: AdWidget(ad: _nativeAd!),
);
}
}
Regras dos Widgets:
StatefulWidget com carregamento no initState()dispose() com ?.dispose()if (mounted) antes de setState()SizedBox.shrink() enquanto não carregadoadUnitId como parâmetro — nunca hardcodedPlaceholder(), Container(color: Colors.grey))// Ads
inject.registerLazySingleton<AdService>(AdService.new);
inject.registerLazySingleton<InterstitialAdService>(InterstitialAdService.new);
// Native ad
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: AdNativeWidget(adUnitId: AdConfig.nativeBanner1),
)
// Banner padrão
AdBannerWidget(adUnitId: AdConfig.banner2)
class _MyViewState extends State<MyView> {
final _interstitialAdService = AppInjector.inject.get<InterstitialAdService>();
@override
void initState() {
super.initState();
_interstitialAdService.load();
Future.delayed(const Duration(seconds: 3), () {
if (mounted) _interstitialAdService.show();
});
}
@override
void dispose() {
// NÃO chame _interstitialAdService.dispose() — é singleton
super.dispose();
}
}
Regras de uso na View:
load() no initState() — pré-carregashow() com Future.delayed — nunca imediatamentemounted antes de show()dispose() do service na View — é singletonbuild()Anúncio inline no conteúdo (lista, feed)?
├─ Layout integrado → AdNativeWidget (TemplateType.medium)
└─ Banner compacto no rodapé → AdBannerWidget (AdSize.banner)
Anúncio de tela cheia ao abrir conteúdo?
└─ InterstitialAdService: load() no initState + show() com delay
Novo slot de anúncio?
└─ Adicione getter estático em AdConfig com IDs Android + iOS
// ❌ IDs hardcoded fora do AdConfig
AdBannerWidget(adUnitId: 'ca-app-pub-XXX/YYY')
// ❌ Inicializar o SDK diretamente
await MobileAds.instance.initialize(); // fora de AdService
// ❌ Exibir intersticial sem delay
void initState() {
super.initState();
_adService.show(); // ERRADO
}
// ❌ InterstitialAdService no Cubit
class MyCubit extends Cubit<MyState> {
MyCubit(this._interstitialAdService); // ERRADO
}
// ❌ Chamar dispose() do singleton
void dispose() {
_interstitialAdService.dispose(); // ERRADO
super.dispose();
}
Última atualização: 28 de março de 2026
testing
Create new skills, modify and improve existing skills, and measure skill performance. Use when users want to create a skill from scratch, edit, or optimize an existing skill, run evals to test a skill, benchmark skill performance with variance analysis, or optimize a skill's description for better triggering accuracy. Activate even when the user says 'create a skill for X', 'the skill is not triggering', 'improve this skill description', 'the agent is not using the skill', 'add a skill to teach the agent how to do X', 'this skill is wrong', or 'update the skill' without explicitly mentioning evals or benchmark.
development
Implements Flutter reusable widgets following the project architecture. Use whenever creating or modifying widgets in presentation/<feature>/widgets/, presentation/<feature>/content/, or common/widgets/. Covers StatelessWidget vs StatefulWidget decision, Entity as parameter, i18n, dispose, componentization rules, and when to access the Cubit via context.read. Activate even when the user says 'extract this to a widget', 'create a list item widget', 'build a reusable card', 'factor out this UI block', 'create a component for this', or 'this View is getting too big' without explicitly mentioning StatelessWidget or reusable components.
tools
Implements Flutter View screens following the project architecture. Use whenever creating or modifying a View (StatefulWidget + Cubit + BlocBuilder), adding a new screen, wiring up BlocBuilder/BlocConsumer/BlocListener, setting up SafeArea, or navigating from the View. Covers State, Cubit, View file, route, DI registration, and common mistakes. Activate even when the user just says "create a screen" or "add a new page", without explicitly mentioning Cubit or BLoC.
testing
Implements Flutter Cubit and State (View Model layer) following the project architecture. Use whenever creating or modifying a Cubit or State class, adding an async method to a Cubit, handling form submission or validation, implementing debounce search, managing loading/error/navigation states, or wiring a Cubit to a Repository or StorageService. Covers sealed States, async patterns with Result<T>, CRUD Cubits, local persistence via StorageService, navigation states, debounce, and common mistakes. Activate even when the user says "add a method", "handle the loading state", or "save locally" without explicitly mentioning Cubit or BLoC.