Flutter Admob's widget get error when setState is call

Viewed 1425

I have a problem with Admob's widgets. I am developing a new feature for a flutter app that contains an Admob banner widget. But when I setState the value of another widget, the Admob Widget gets an error.

I am using :google_mobile_ads: ^0.11.0+1

The banner is build like so:

      @override
      void initState() {
        setState(() {
          _adBanner = createBannerAd();
        });
        super.initState();
      }

      @override
      void dispose() {
        _adBanner.dispose();
        super.dispose();
      }

And the widget is display like this:

    Container(
        margin: EdgeInsets.only(bottom: myPercent(2, screenHeight)),
        child: FutureBuilder(
            future: _adBanner.load(),
            builder: (context, snapshot) {
              if (snapshot.connectionState == ConnectionState.done) {
                return Container(
                  margin: EdgeInsets.only(bottom: 3),
                  width: myPercent(95, screenWidth),
                  height: myPercent(6, screenHeight),
                  alignment: Alignment.center,
                  child: AdWidget(
                    ad: _adBanner,
                  ),
                );
              }
              return Container();
            }),

The log error catch :

    flutter: click
    flutter: ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY   ╞═══════════════════════════════════════════════════════════
flutter: The following assertion was thrown building AdWidget(dirty, state: _AdWidgetState#a1afb):
flutter: This AdWidget is already in the Widget tree
flutter: If you placed this AdWidget in a list, make sure you create a new instance in the builder function
flutter: with a unique ad object.
flutter: Make sure you are not using the same ad object in more than one AdWidget.
flutter:
flutter: The relevant error-causing widget was:
flutter:   AdWidget file:///Users/sofian/Work/Personal/Mobile/WhatUDo/what_u_do/lib/views/idea.dart:295:34
flutter:
flutter: When the exception was thrown, this was the stack:
flutter: #0      _AdWidgetState.build (package:google_mobile_ads/src/ad_containers.dart:372:7)
flutter: #1      StatefulElement.build (package:flutter/src/widgets/framework.dart:4825:27)
flutter: #2      ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4708:15)
flutter: #3      StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:4880:11)
flutter: #4      BuildOwner._runWithCurrentBuildTarget (package:flutter/src/widgets/framework.dart:2708:15)
flutter: #5      Element.rebuild (package:flutter/src/widgets/framework.dart:4407:12)
flutter: #6      ComponentElement._firstBuild (package:flutter/src/widgets/framework.dart:4687:5)
flutter: #7      StatefulElement._firstBuild (package:flutter/src/widgets/framework.dart:4871:11)
flutter: #8      ComponentElement.mount (package:flutter/src/widgets/framework.dart:4682:5)
flutter: ...     Normal element mounting (10 frames)
flutter: #18     Element.inflateWidget (package:flutter/src/widgets/framework.dart:3660:14)
flutter: #19     Element.updateChild (package:flutter/src/widgets/framework.dart:3422:20)
flutter: #20     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4733:16)
flutter: #21     BuildOwner._runWithCurrentBuildTarget (package:flutter/src/widgets/framework.dart:2708:15)
flutter: #22     Element.rebuild (package:flutter/src/widgets/framework.dart:4407:12)
flutter: #23     StatelessElement.update (package:flutter/src/widgets/framework.dart:4789:5)
flutter: #24     Element.updateChild (package:flutter/src/widgets/framework.dart:3412:15)
flutter: #25     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4733:16)
flutter: #26     StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:4880:11)
flutter: #27     BuildOwner._runWithCurrentBuildTarget (package:flutter/src/widgets/framework.dart:2708:15)
flutter: #28     Element.rebuild (package:flutter/src/widgets/framework.dart:4407:12)
flutter: #29     BuildOwner.buildScope (package:flutter/src/widgets/framework.dart:2813:33)
flutter: #30     WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:899:21)
flutter: #31     RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:320:5)
flutter: #32     SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1119:15)
flutter: #33     SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1057:9)
flutter: #34     SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:973:5)
flutter: #38     _invoke (dart:ui/hooks.dart:157:10)
flutter: #39     PlatformDispatcher._drawFrame (dart:ui/platform_dispatcher.dart:253:5)
flutter: #40     _drawFrame (dart:ui/hooks.dart:120:31)
flutter: (elided 3 frames from dart:async)

enter image description here

4 Answers

As from question and error log, seems like you are trying to fresh screen/widget using setState but not the actual ad based container widget - still you are getting this error.

If I understood correctly.. then issue is about ad widget is trying to rebuild on setState call with older ad object but it expect new ad object everytime new build. So, avoid those kind of widget build if that's not required.

create a seprate ad based container widget like AppXyzAdWidget and move the ad's parent container code and other ad related code inside the new widget and use the newly created ad based widget on your screen.

This way you can decouple your screen without ad related things then after setState will not reload your ad and their widget just only refresh your content.

Calling setState() causes the widget to rebuild, so your FutureBuilder() widget fires the future task again. This is why you're seeing this error.

You need to remove FutureBuilder() and move your future task to initState():

// ...
// Some code

@override
void initState() {
  setState(() {
    _adBanner = createBannerAd();
  });

  _adBanner.load().whenComplete(() {
    if (this.mounted) {
      setState(() {
        _showAdBanner = true;
      });
    }
  });
  super.initState();
}

@override
void dispose() {
  _adBanner.dispose();
  super.dispose();
}

// ...
// Some code

You can also have boolean to make the AdWidget() appear when the future task is complete:

bool _showAdBanner = false;

// ...
// Some code

_showAdBanner
  ? Container(
  margin: EdgeInsets.only(bottom: 3),
  width: myPercent(95, screenWidth),
  height: myPercent(6, screenHeight),
  alignment: Alignment.center,
  child: AdWidget(
    ad: _adBanner,
  ),
)
  : Container()

// ...
// Some code

According to the google ads package example

googleads-mobile-flutter

You have to use didChangeDependencies

  class BannarAdWidget extends StatefulWidget {
   const BannarAdWidget({Key? key}) : super(key: key);

   @override
   State<BannarAdWidget> createState() => _BannarAdWidgetState();
  }

  class _BannarAdWidgetState extends State<BannarAdWidget> {
   BannerAd? _bannerAd;
   bool _bannerAdIsLoaded = false;

   @override
   void didChangeDependencies() {
   // Create the ad objects and load ads.
   _bannerAd = BannerAd(
    size: AdSize.banner,
    request: const AdRequest(),
    adUnitId: AdmobHelper.bannerAdUnitId,
    listener: BannerAdListener(
      onAdLoaded: (Ad ad) {
        print('$BannerAd loaded.');
        setState(() => _bannerAdIsLoaded = true);
      },
      onAdFailedToLoad: (Ad ad, LoadAdError error) {
        print('$BannerAd failedToLoad: $error');
        ad.dispose();
      },
      onAdOpened: (Ad ad) => print('$BannerAd onAdOpened.'),
      onAdClosed: (Ad ad) => print('$BannerAd onAdClosed.'),
    ),
    )..load();

   super.didChangeDependencies();
  }

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

  @override
  Widget build(BuildContext context) {
   final BannerAd? bannerAd = _bannerAd;
   if (_bannerAdIsLoaded && bannerAd != null) {
    return SizedBox(
      height: bannerAd.size.height.toDouble(),
      width: bannerAd.size.width.toDouble(),
      child: AdWidget(ad: bannerAd),
    );
   } else {
    return const SizedBox.shrink();
   }
  }



  class AdmobHelper {
    static String get bannerAdUnitId {
     if (Platform.isAndroid) {
      return "ca-app-pub-3940256099942544/6300978111";
     } else if (Platform.isIOS) {
      return "ca-app-pub-3940256099942544/2934735716";
     } else {
      throw UnsupportedError('Unsupported platform');
    }
   }
  }

Can you remove initState code and initialize _adBanner in your build function like this ?

@override
  Widget build(BuildContext context) {
    _adBanner = createBannerAd();
    return Container(
    margin: EdgeInsets.only(bottom: myPercent(2, screenHeight)),
    child: FutureBuilder(
        future: _adBanner.load(),
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.done) {
            return Container(
              margin: EdgeInsets.only(bottom: 3),
              width: myPercent(95, screenWidth),
              height: myPercent(6, screenHeight),
              alignment: Alignment.center,
              child: AdWidget(
                ad: _adBanner,
              ),
            );
          }
          return Container();
        });
  }
Related