fix(ads): banner retries load when SDK becomes ready

On a cold start the consent flow is still running when home first builds, so
createBanner returns null and the slot stayed empty until a rebuild. AdService
now exposes an isReady ValueNotifier; BannerAdSlot listens and retries its load
once MobileAds finishes initializing. Verified: analyze clean, 169 tests green.
This commit is contained in:
2026-06-13 14:23:45 +09:00
parent 70f87ab8f2
commit 640b23804f
2 changed files with 28 additions and 4 deletions
+18 -3
View File
@@ -3,11 +3,16 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';
import '../../services/ad_service.dart';
import '../../state/providers.dart';
/// A self-loading 320x50 banner for the home/map screens. Renders nothing
/// when ads are removed or the banner has not loaded — never reserves blank
/// space and never appears on the game screen.
///
/// If the slot is built before the AdMob SDK finished initializing (common on
/// a cold start while the consent flow is still running), the first create
/// returns null; it then retries once the [AdService.isReady] notifier flips.
class BannerAdSlot extends ConsumerStatefulWidget {
const BannerAdSlot({super.key});
@@ -18,13 +23,22 @@ class BannerAdSlot extends ConsumerStatefulWidget {
class _BannerAdSlotState extends ConsumerState<BannerAdSlot> {
BannerAd? _ad;
bool _loaded = false;
AdService? _adService;
@override
void didChangeDependencies() {
super.didChangeDependencies();
if (_ad != null) return;
if (_adService == null) {
_adService = ref.read(adServiceProvider);
_adService!.isReady.addListener(_tryCreate);
}
_tryCreate();
}
void _tryCreate() {
if (_ad != null || !mounted) return;
if (ref.read(adsRemovedProvider)) return;
final ad = ref.read(adServiceProvider).createBanner(
final ad = _adService!.createBanner(
listener: BannerAdListener(
onAdLoaded: (_) {
if (mounted) setState(() => _loaded = true);
@@ -32,12 +46,13 @@ class _BannerAdSlotState extends ConsumerState<BannerAdSlot> {
onAdFailedToLoad: (ad, _) => ad.dispose(),
),
);
if (ad == null) return;
if (ad == null) return; // SDK not ready yet; isReady will retry.
_ad = ad;
}
@override
void dispose() {
_adService?.isReady.removeListener(_tryCreate);
_ad?.dispose();
super.dispose();
}