Skip to content

Commit 4ac3936

Browse files
committed
fix(ads): improve ad loading state management and error handling
- Enhance comments to explain state management and error prevention - Implement checks to prevent multiple completions of the loadAd completer - Refine ad loading logic to handle widget updates and disposals gracefully - Optimize state transitions to ensure smooth UI updates
1 parent bbeb631 commit 4ac3936

File tree

1 file changed

+35
-14
lines changed

1 file changed

+35
-14
lines changed

lib/ads/widgets/ad_loader_widget.dart

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,9 @@ class _AdLoaderWidgetState extends State<AdLoaderWidget> {
5858
final AdCacheService _adCacheService = AdCacheService();
5959

6060
// Completer to manage the lifecycle of the ad loading future.
61-
// This helps in cancelling pending operations if the widget is disposed.
61+
// This helps in cancelling pending operations if the widget is disposed
62+
// or updated, preventing `setState` calls on an unmounted widget
63+
// and avoiding `StateError` from completing a completer multiple times.
6264
Completer<void>? _loadAdCompleter;
6365

6466
@override
@@ -78,14 +80,24 @@ class _AdLoaderWidgetState extends State<AdLoaderWidget> {
7880
'AdLoaderWidget updated for new placeholder ID: '
7981
'${widget.adPlaceholder.id}. Re-loading ad.',
8082
);
81-
// Cancel the previous loading operation if it's still active and not yet completed.
83+
// Cancel the previous loading operation if it's still active and not yet
84+
// completed. This prevents a race condition if a new load is triggered
85+
// while an old one is still in progress.
8286
if (_loadAdCompleter != null && !_loadAdCompleter!.isCompleted) {
8387
_loadAdCompleter?.completeError(
8488
StateError('Ad loading cancelled: Widget updated with new ID.'),
8589
);
8690
}
87-
_loadAdCompleter = null; // Clear the old completer
88-
_loadedAd = null; // Clear the old ad
91+
_loadAdCompleter = null; // Clear the old completer for the new load
92+
93+
// Immediately set the widget to a loading state to prevent UI flicker.
94+
// This ensures a smooth transition from the old ad (or no ad) to the
95+
// loading indicator for the new ad.
96+
setState(() {
97+
_loadedAd = null;
98+
_isLoading = true;
99+
_hasError = false;
100+
});
89101
_loadAd(); // Start loading the new ad
90102
}
91103
}
@@ -118,10 +130,6 @@ class _AdLoaderWidgetState extends State<AdLoaderWidget> {
118130
// if the widget is removed from the tree while the async operation
119131
// is still in progress.
120132
if (!mounted) return;
121-
setState(() {
122-
_isLoading = true;
123-
_hasError = false;
124-
});
125133
// Attempt to retrieve the ad from the cache first.
126134
final cachedAd = _adCacheService.getAd(widget.adPlaceholder.id);
127135

@@ -135,7 +143,11 @@ class _AdLoaderWidgetState extends State<AdLoaderWidget> {
135143
_loadedAd = cachedAd;
136144
_isLoading = false;
137145
});
138-
_loadAdCompleter?.complete(); // Complete the completer on success
146+
// Complete the completer only if it hasn't been completed already
147+
// (e.g., by dispose() or didUpdateWidget() cancelling an old load).
148+
if (_loadAdCompleter?.isCompleted == false) {
149+
_loadAdCompleter!.complete(); // Complete the completer on success
150+
}
139151
return;
140152
}
141153

@@ -164,7 +176,10 @@ class _AdLoaderWidgetState extends State<AdLoaderWidget> {
164176
_loadedAd = adFeedItem.nativeAd;
165177
_isLoading = false;
166178
});
167-
_loadAdCompleter?.complete(); // Complete the completer on success
179+
// Complete the completer only if it hasn't been completed already.
180+
if (_loadAdCompleter?.isCompleted == false) {
181+
_loadAdCompleter!.complete(); // Complete the completer on success
182+
}
168183
} else {
169184
_logger.warning(
170185
'Failed to load ad for placeholder ID: ${widget.adPlaceholder.id}. No ad returned.',
@@ -175,9 +190,12 @@ class _AdLoaderWidgetState extends State<AdLoaderWidget> {
175190
_hasError = true;
176191
_isLoading = false;
177192
});
178-
_loadAdCompleter?.completeError(
179-
StateError('Failed to load ad: No ad returned.'),
180-
); // Complete with error
193+
// Complete the completer with an error only if it hasn't been completed already.
194+
if (_loadAdCompleter?.isCompleted == false) {
195+
_loadAdCompleter?.completeError(
196+
StateError('Failed to load ad: No ad returned.'),
197+
); // Complete with error
198+
}
181199
}
182200
} catch (e, s) {
183201
_logger.severe(
@@ -191,7 +209,10 @@ class _AdLoaderWidgetState extends State<AdLoaderWidget> {
191209
_hasError = true;
192210
_isLoading = false;
193211
});
194-
_loadAdCompleter?.completeError(e); // Complete with error
212+
// Complete the completer with an error only if it hasn't been completed already.
213+
if (_loadAdCompleter?.isCompleted == false) {
214+
_loadAdCompleter?.completeError(e); // Complete with error
215+
}
195216
}
196217
}
197218

0 commit comments

Comments
 (0)