'This AdWidget is already in the Widget tree' — It Only Crashed After One Specific Flow

Published: (April 2, 2026 at 04:01 PM EDT)
3 min read
Source: Dev.to

Source: Dev.to

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 message was clear, but the crash was not consistent.

Observed behavior

FlowResult
Switch tabs normallyNo crash
Go to another page and come backNo crash
Update address → go back to main page → open MyPage tabCrash

The address‑update flow was the only one that triggered the error.

Why it happened

The app uses a BottomNavigationBar with an IndexedStack. After the address update the code navigated back to the main page like this:

Navigator.push(
  context,
  MaterialPageRoute(builder: (context) => MainPage()),
);

Navigator.push adds a new page on top of the stack; it does not remove the old one. Consequently, the previous MainPage (and its MyPage inside it) remained alive in memory.

When MyPage was opened on the new MainPage, the old MyPage’s state still held a BannerAd with its AdWidget mounted. Now two AdWidgets tried to use the same ad resources simultaneously, causing the crash.

In normal tab switching there is only one MainPage and one MyPage state, so the problem never appears.

Fix: clear the navigation stack

Replace the push with pushAndRemoveUntil:

Navigator.pushAndRemoveUntil(
  context,
  MaterialPageRoute(builder: (_) => MainPage()),
  (route) => false,
);

This removes all previous routes, ensuring the old MainPage and its MyPage are disposed. When the user opens MyPage again, a fresh instance is created, eliminating duplicate AdWidgets. After this change the crash stopped occurring.

didUpdateWidget for Ads

The original code recreated the banner ad on every widget update:

@override
void didUpdateWidget(covariant MyPage oldWidget) {
  super.didUpdateWidget(oldWidget);
  _bannerAd?.dispose();
  _bannerAd = AdHelper.createBannerAd();
}

If the old AdWidget was still in the tree, disposing and recreating the ad caused the same duplication problem. The fix was to remove this logic entirely and manage the banner ad only in initState and dispose:

@override
void initState() {
  super.initState();
  _bannerAd = AdHelper.createBannerAd();
}

@override
void dispose() {
  _bannerAd?.dispose();
  super.dispose();
}

How the plugin enforces the rule

The google_mobile_ads plugin tracks whether an ad is already mounted in the widget tree. In ad_containers.dart:

  • _AdWidgetState.initState checks the ad’s ID via instanceManager.isWidgetAdIdMounted().
  • If the ad is already mounted, the build method throws the “already in the Widget tree” error.
  • The ID is released only when _AdWidgetState.dispose calls unmountWidgetAdId.

Thus, a single BannerAd can be used by only one AdWidget at a time; the plugin enforces this at the code level.

Takeaway

Navigator.push keeps the previous page (and any native resources it owns) alive. When those resources—such as ads—must be disposed, the old page’s state must be removed from the navigation stack. Use pushAndRemoveUntil (or other stack‑clearing navigation methods) to ensure proper disposal and avoid duplicate AdWidget crashes.

0 views
Back to Blog

Related posts

Read more »