The Error
I was working on a Flutter app that uses google_mobile_ads for banner ads. One day, I got this error on the MyPage screen:
This AdWidget is already in the Widget tree
Make sure you are not using the same ad object in more than one AdWidget.
The error message was clear. But the weird thing was — it didn't always happen.
Finding the Pattern
I tested different scenarios and found a pattern:
- Switch tabs normally → no crash
- Go to another page and come back → no crash
- Update address → go back to main page → open MyPage tab → crash
So the question became: what's different about the address update flow?
What Was Different
The app uses BottomNavigationBar with IndexedStack. After the address update, the code navigated back to the main page like this:
Navigator.push(
context,
MaterialPageRoute(builder: (context) => MainPage()),
);
This looks normal. But Navigator.push adds a new page on top of the stack. It does not remove the old one. So the old MainPage — and the old MyPage inside it — was still alive in memory.
When I opened MyPage on the new MainPage, the old MyPage's State was still holding a BannerAd with its AdWidget mounted. Now there were two AdWidgets trying to use the same ad resources at the same time. That's why it crashed.
In normal tab switching, this never happened because there was only one MainPage and one MyPage State.
The Fix
I changed Navigator.push to Navigator.pushAndRemoveUntil:
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (_) => MainPage()),
(route) => false,
);
This clears the entire navigation stack. The old MainPage and its MyPage get properly disposed. When the user opens MyPage again, it's a completely new instance — no duplicate AdWidget.
After this change, the crash never happened again.
I Also Removed didUpdateWidget for Ads
The original code had this:
@override
void didUpdateWidget(covariant MyPage oldWidget) {
super.didUpdateWidget(oldWidget);
_bannerAd?.dispose();
_bannerAd = AdHelper.createBannerAd();
}
This recreated the ad every time the parent widget rebuilt. But if the old AdWidget was still in the tree, disposing and recreating the ad caused the same duplication problem. I removed this entirely. Banner ads should only be created in initState and cleaned up in dispose.
Key Takeaway
The google_mobile_ads plugin internally tracks whether each ad is already mounted in the widget tree. In the source code (ad_containers.dart), _AdWidgetState.initState checks the ad's ID through instanceManager.isWidgetAdIdMounted(). If the ad is already mounted somewhere, the build method throws the "already in the Widget tree" error. The ad ID is only released when _AdWidgetState.dispose calls unmountWidgetAdId. This means one BannerAd can only be used by one AdWidget at a time — the plugin enforces this at the code level.
The real lesson was about navigation. Navigator.push keeps the old page alive. If that page owns native resources like ads, those resources stay alive too — and the old AdWidget never gets disposed, so the ad ID is never released. When you need to "reset" to the main page, use pushAndRemoveUntil to make sure the old state is fully disposed.
Top comments (0)