Skip to content

Commit b669a6b

Browse files
committed
fix(ads): prevent setState() calls on disposed widget
- Add Completer to manage ad loading lifecycle - Cancel pending operations in `dispose()` method - Complete or error Completer based on ad loading outcome - Prevents "setState() called after dispose()" error in AdLoaderWidget
1 parent 1445db5 commit b669a6b

File tree

1 file changed

+26
-0
lines changed

1 file changed

+26
-0
lines changed

lib/ads/widgets/ad_loader_widget.dart

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import 'dart:async';
2+
13
import 'package:core/core.dart';
24
import 'package:flutter/material.dart';
35
import 'package:flutter_news_app_mobile_client_full_source_code/ads/ad_cache_service.dart';
@@ -55,18 +57,36 @@ class _AdLoaderWidgetState extends State<AdLoaderWidget> {
5557
final Logger _logger = Logger('AdLoaderWidget');
5658
final AdCacheService _adCacheService = AdCacheService();
5759

60+
// Completer to manage the lifecycle of the ad loading future.
61+
// This helps in cancelling pending operations if the widget is disposed.
62+
Completer<void>? _loadAdCompleter;
63+
5864
@override
5965
void initState() {
6066
super.initState();
6167
_loadAd();
6268
}
6369

70+
@override
71+
void dispose() {
72+
// Cancel any pending ad loading operation when the widget is disposed.
73+
// This prevents `setState()` calls on a disposed widget.
74+
_loadAdCompleter?.completeError(
75+
StateError('Ad loading cancelled: Widget disposed.'),
76+
);
77+
_loadAdCompleter = null;
78+
super.dispose();
79+
}
80+
6481
/// Loads the native ad for this slot.
6582
///
6683
/// This method first checks the [AdCacheService] for a pre-loaded ad.
6784
/// If found, it uses the cached ad. Otherwise, it requests a new ad
6885
/// from the [AdService] and stores it in the cache upon success.
6986
Future<void> _loadAd() async {
87+
// Initialize a new completer for this loading operation.
88+
_loadAdCompleter = Completer<void>();
89+
7090
// Ensure the widget is still mounted before calling setState.
7191
// This prevents the "setState() called after dispose()" error
7292
// if the widget is removed from the tree while the async operation
@@ -89,6 +109,7 @@ class _AdLoaderWidgetState extends State<AdLoaderWidget> {
89109
_loadedAd = cachedAd;
90110
_isLoading = false;
91111
});
112+
_loadAdCompleter?.complete(); // Complete the completer on success
92113
return;
93114
}
94115

@@ -117,6 +138,7 @@ class _AdLoaderWidgetState extends State<AdLoaderWidget> {
117138
_loadedAd = adFeedItem.nativeAd;
118139
_isLoading = false;
119140
});
141+
_loadAdCompleter?.complete(); // Complete the completer on success
120142
} else {
121143
_logger.warning(
122144
'Failed to load ad for placeholder ID: ${widget.adPlaceholder.id}. No ad returned.',
@@ -127,6 +149,9 @@ class _AdLoaderWidgetState extends State<AdLoaderWidget> {
127149
_hasError = true;
128150
_isLoading = false;
129151
});
152+
_loadAdCompleter?.completeError(
153+
StateError('Failed to load ad: No ad returned.'),
154+
); // Complete with error
130155
}
131156
} catch (e, s) {
132157
_logger.severe(
@@ -140,6 +165,7 @@ class _AdLoaderWidgetState extends State<AdLoaderWidget> {
140165
_hasError = true;
141166
_isLoading = false;
142167
});
168+
_loadAdCompleter?.completeError(e); // Complete with error
143169
}
144170
}
145171

0 commit comments

Comments
 (0)