640b23804f
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.
73 lines
2.1 KiB
Dart
73 lines
2.1 KiB
Dart
// lib/ui/widgets/banner_ad_slot.dart
|
|
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});
|
|
|
|
@override
|
|
ConsumerState<BannerAdSlot> createState() => _BannerAdSlotState();
|
|
}
|
|
|
|
class _BannerAdSlotState extends ConsumerState<BannerAdSlot> {
|
|
BannerAd? _ad;
|
|
bool _loaded = false;
|
|
AdService? _adService;
|
|
|
|
@override
|
|
void didChangeDependencies() {
|
|
super.didChangeDependencies();
|
|
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 = _adService!.createBanner(
|
|
listener: BannerAdListener(
|
|
onAdLoaded: (_) {
|
|
if (mounted) setState(() => _loaded = true);
|
|
},
|
|
onAdFailedToLoad: (ad, _) => ad.dispose(),
|
|
),
|
|
);
|
|
if (ad == null) return; // SDK not ready yet; isReady will retry.
|
|
_ad = ad;
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_adService?.isReady.removeListener(_tryCreate);
|
|
_ad?.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final ad = _ad;
|
|
if (ad == null || !_loaded || ref.watch(adsRemovedProvider)) {
|
|
return const SizedBox.shrink();
|
|
}
|
|
return SizedBox(
|
|
width: ad.size.width.toDouble(),
|
|
height: ad.size.height.toDouble(),
|
|
child: AdWidget(ad: ad),
|
|
);
|
|
}
|
|
}
|