@@ -3,6 +3,7 @@ import 'package:flutter_news_app_mobile_client_full_source_code/ads/ad_provider.
33import 'package:flutter_news_app_mobile_client_full_source_code/ads/models/ad_theme_style.dart' ;
44import 'package:flutter_news_app_mobile_client_full_source_code/ads/models/inline_ad.dart' ;
55import 'package:flutter_news_app_mobile_client_full_source_code/ads/models/interstitial_ad.dart' ;
6+ import 'package:flutter_news_app_mobile_client_full_source_code/app/config/app_environment.dart' ;
67import 'package:logging/logging.dart' ;
78
89/// {@template ad_service}
@@ -20,13 +21,21 @@ class AdService {
2021 /// These providers will be used to load ads from specific ad networks.
2122 AdService ({
2223 required Map <AdPlatformType , AdProvider > adProviders,
24+ required AppEnvironment environment,
2325 Logger ? logger,
2426 }) : _adProviders = adProviders,
27+ _environment = environment,
2528 _logger = logger ?? Logger ('AdService' );
2629
2730 final Map <AdPlatformType , AdProvider > _adProviders;
31+ final AppEnvironment _environment;
2832 final Logger _logger;
2933
34+ // Configurable retry parameters for ad loading.
35+ // TODO(fulleni): Make this configurable through the remote config.
36+ static const int _maxAdLoadRetries = 2 ;
37+ static const Duration _adLoadRetryDelay = Duration (seconds: 1 );
38+
3039 /// Initializes all underlying ad providers.
3140 ///
3241 /// This should be called once at application startup to ensure all
@@ -39,6 +48,41 @@ class AdService {
3948 _logger.info ('AdService: AdService initialized.' );
4049 }
4150
51+ /// Disposes of an ad object by delegating to the appropriate [AdProvider] .
52+ ///
53+ /// This method is called by the [InlineAdCacheService] to ensure that
54+ /// inline ad resources are released when an ad is removed from the cache
55+ /// or replaced. It also handles disposal of interstitial ads.
56+ Future <void > disposeAd (dynamic adModel) async {
57+ // Determine the AdPlatformType from the adModel if it's an InlineAd or InterstitialAd.
58+ AdPlatformType ? providerType;
59+ Object ? adObject; // To hold the actual ad object
60+
61+ if (adModel is InlineAd ) {
62+ providerType = adModel.provider;
63+ adObject = adModel.adObject;
64+ } else if (adModel is InterstitialAd ) {
65+ providerType = adModel.provider;
66+ adObject = adModel.adObject;
67+ }
68+
69+ if (providerType != null && adObject != null ) {
70+ final adProvider = _adProviders[providerType];
71+ if (adProvider != null ) {
72+ await adProvider.disposeAd (adObject); // Pass the actual ad object
73+ } else {
74+ _logger.warning (
75+ 'AdService: No AdProvider found for type $providerType to dispose ad.' ,
76+ );
77+ }
78+ } else {
79+ _logger.warning (
80+ 'AdService: Cannot determine AdPlatformType or ad object for ad model of type '
81+ '${adModel .runtimeType }. Cannot dispose ad.' ,
82+ );
83+ }
84+ }
85+
4286 /// Retrieves a loaded inline ad (native or banner) for display in a feed.
4387 ///
4488 /// This method delegates the ad loading to the appropriate [AdProvider]
@@ -95,6 +139,18 @@ class AdService {
95139 }
96140
97141 final primaryAdPlatform = adConfig.primaryAdPlatform;
142+
143+ // If RemoteConfig specifies AdPlatformType.demo but the app is not in demo environment,
144+ // log a warning and skip ad load.
145+ if (primaryAdPlatform == AdPlatformType .demo &&
146+ _environment != AppEnvironment .demo) {
147+ _logger.warning (
148+ 'AdService: RemoteConfig specifies AdPlatformType.demo as primary '
149+ 'ad platform, but app is not in demo environment. Skipping interstitial ad load.' ,
150+ );
151+ return null ;
152+ }
153+
98154 final adProvider = _adProviders[primaryAdPlatform];
99155
100156 if (adProvider == null ) {
@@ -224,6 +280,18 @@ class AdService {
224280 }
225281
226282 final primaryAdPlatform = adConfig.primaryAdPlatform;
283+
284+ // If RemoteConfig specifies AdPlatformType.demo but the app is not in demo environment,
285+ // log a warning and skip ad load.
286+ if (primaryAdPlatform == AdPlatformType .demo &&
287+ _environment != AppEnvironment .demo) {
288+ _logger.warning (
289+ 'AdService: RemoteConfig specifies AdPlatformType.demo as primary '
290+ 'ad platform, but app is not in demo environment. Skipping inline ad load.' ,
291+ );
292+ return null ;
293+ }
294+
227295 final adProvider = _adProviders[primaryAdPlatform];
228296
229297 if (adProvider == null ) {
@@ -258,53 +326,69 @@ class AdService {
258326 return null ;
259327 }
260328
261- _logger.info (
262- 'AdService: Requesting $adType ad from $primaryAdPlatform AdProvider with ID: $adId '
263- 'for ${feedAd ? 'feed' : 'in-article' } placement.' ,
264- );
265- try {
266- InlineAd ? loadedAd;
267- // For in-article banner ads, bannerAdShape dictates the visual style.
268- // For feed ads, headlineImageStyle is still relevant.
269- final effectiveHeadlineImageStyle = feedAd ? headlineImageStyle : null ;
270-
271- switch (adType) {
272- case AdType .native :
273- loadedAd = await adProvider.loadNativeAd (
274- adPlatformIdentifiers: platformAdIdentifiers,
275- adId: adId,
276- adThemeStyle: adThemeStyle,
277- headlineImageStyle: effectiveHeadlineImageStyle,
278- );
279- case AdType .banner:
280- loadedAd = await adProvider.loadBannerAd (
281- adPlatformIdentifiers: platformAdIdentifiers,
282- adId: adId,
283- adThemeStyle: adThemeStyle,
284- headlineImageStyle: effectiveHeadlineImageStyle,
285- );
286- case AdType .interstitial:
287- case AdType .video:
288- _logger.warning (
289- 'AdService: Attempted to load $adType ad using _loadInlineAd. This is not supported.' ,
290- );
291- return null ;
329+ for (var attempt = 0 ; attempt <= _maxAdLoadRetries; attempt++ ) {
330+ if (attempt > 0 ) {
331+ _logger.info (
332+ 'AdService: Retrying $adType ad load (attempt $attempt ) for ID: $adId '
333+ 'after $_adLoadRetryDelay delay.' ,
334+ );
335+ await Future <void >.delayed (_adLoadRetryDelay);
292336 }
293337
294- if (loadedAd != null ) {
295- _logger.info ('AdService: $adType ad successfully loaded.' );
296- return loadedAd;
297- } else {
298- _logger.info ('AdService: No $adType ad loaded by AdProvider.' );
299- return null ;
338+ try {
339+ _logger.info (
340+ 'AdService: Requesting $adType ad from $primaryAdPlatform AdProvider with ID: $adId '
341+ 'for ${feedAd ? 'feed' : 'in-article' } placement.' ,
342+ );
343+ InlineAd ? loadedAd;
344+ // For in-article banner ads, bannerAdShape dictates the visual style.
345+ // For feed ads, headlineImageStyle is still relevant.
346+ final effectiveHeadlineImageStyle = feedAd ? headlineImageStyle : null ;
347+
348+ switch (adType) {
349+ case AdType .native :
350+ loadedAd = await adProvider.loadNativeAd (
351+ adPlatformIdentifiers: platformAdIdentifiers,
352+ adId: adId,
353+ adThemeStyle: adThemeStyle,
354+ headlineImageStyle: effectiveHeadlineImageStyle,
355+ );
356+ case AdType .banner:
357+ loadedAd = await adProvider.loadBannerAd (
358+ adPlatformIdentifiers: platformAdIdentifiers,
359+ adId: adId,
360+ adThemeStyle: adThemeStyle,
361+ headlineImageStyle: effectiveHeadlineImageStyle,
362+ );
363+ case AdType .interstitial:
364+ case AdType .video:
365+ _logger.warning (
366+ 'AdService: Attempted to load $adType ad using _loadInlineAd. This is not supported.' ,
367+ );
368+ return null ;
369+ }
370+
371+ if (loadedAd != null ) {
372+ _logger.info ('AdService: $adType ad successfully loaded.' );
373+ return loadedAd;
374+ } else {
375+ _logger.info ('AdService: No $adType ad loaded by AdProvider.' );
376+ // If no ad is returned, it might be a "no fill" scenario.
377+ // Continue to the next retry attempt.
378+ }
379+ } catch (e, s) {
380+ _logger.severe (
381+ 'AdService: Error getting $adType ad from AdProvider on attempt $attempt : $e ' ,
382+ e,
383+ s,
384+ );
385+ // If an exception occurs, log it and continue to the next retry attempt.
300386 }
301- } catch (e, s) {
302- _logger.severe (
303- 'AdService: Error getting $adType ad from AdProvider: $e ' ,
304- e,
305- s,
306- );
307- return null ;
308387 }
388+
389+ _logger.warning (
390+ 'AdService: All retry attempts failed for $adType ad with ID: $adId .' ,
391+ );
392+ return null ;
309393 }
310394}
0 commit comments