简体   繁体   中英

Flutter :- This AdWidget is already in the Widget tree. How to disable this exception. And what does it mean?

So I insert admob ads inside the List. And I added the functionality of infinity scroll inside the list view. So when the user scrolls to end of the list, new items are added into the list. With this items I also add admob ads inside it.

So when the users scroll to the end the new items and ads are added into the List. At that time the below exceptions are caught. So how to solve this exception.

======== Exception caught by widgets library =======================================================
The following assertion was thrown building AdWidget-[#53ef3](dirty, state: _AdWidgetState#850ac):
This AdWidget is already in the Widget tree


If you placed this AdWidget in a list, make sure you create a new instance in the builder function with a unique ad object.
Make sure you are not using the same ad object in more than one AdWidget.

The relevant error-causing widget was: 
  AdWidget-[#53ef3] file:///D:/flutter%20project/memer/lib/pages/TimeLinePage.dart:198:42
When the exception was thrown, this was the stack: 
#0      _AdWidgetState.build (package:google_mobile_ads/src/ad_containers.dart:371:7)
#1      StatefulElement.build (package:flutter/src/widgets/framework.dart:4612:27)
#2      ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4495:15)
#3      StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:4667:11)
#4      Element.rebuild (package:flutter/src/widgets/framework.dart:4189:5)

code:-

return ListView.builder(itemBuilder: (context, index){
        //print(posts);
        if(posts[index] is Post){
          return posts[index];
        }
        else{
          final Container adContainer = Container(
                                  alignment: Alignment.center,
                                  child: AdWidget(key: UniqueKey(), ad: posts[index] as BannerAd),//AdmobService.createBannerAd()..load()
                                  height: 50,
                              );
                      return adContainer;
        }
      },itemCount: posts.length,
          controller: scrollController,physics: const BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()));
    }

In addition to Kafil Khan's answer , you can wrap the Container widget with StatefulBuilder .

Example:

Widget bannerAdWidget() {
    return StatefulBuilder(
      builder: (context, setState) => Container(
        child: AdWidget(ad: _bannerAd),
        width: _bannerAd.size.width.toDouble(),
        height: 100.0,
        alignment: Alignment.center,
      ),
    );
  }

The problem is that you're putting the same Widget again and again. You can fix this by creating a new StatefulWidget class and returning the Adwidget, this will build that same widget multiple times, it works like a Builder. This solved my problem, hope it will work for you too: :)

You also don't have to provide multiple id's for a single ad unit.

When you want to add a new banner, you have to assign it a new ID:

BannerAd(adUnitId: 'somethingDifferentThanTheOneInTheTree')

As clearly stated by the error log:

If you placed this AdWidget in a list, make sure you create a new instance in the builder function with a unique ad object. Make sure you are not using the same ad object in more than one AdWidget.

As the above answer , just I will put my implementation here for newcomers from the future to be much easier for them.

Firstly, let's create a helper class called ad_helper to hide all stuff from our UI, Which contains two helper methods:

First method is buildBannerWidget which is a public method to build our ad widget as we wish.

Second method is _instantiateBanner which is a private method to build our bannerAd object every time we call buildBannerWidget method.

class Ads {
 static BannerAd? _banner;

  static Future<Widget> buildBannerWidget({
    required BuildContext context,
  }) async {
    final mediaQuery = MediaQuery.of(context);

    await _instantiateBanner(
      mediaQuery.orientation,
      mediaQuery.size.width.toInt(),
    );

    return Container(
      width : MediaQuery.of(context).size.width,
      height : 70,
      child: AdWidget(ad: _banner!),
    );
  }

  static Future<BannerAd> _instantiateBanner(orientation, width) async {
    _banner = BannerAd(
      adUnitId: BannerAd.testAdUnitId,
      //  size: AdSize.banner,
      size: (await AdSize.getAnchoredAdaptiveBannerAdSize(orientation, width))!,
      request: _getBannerAdRequest(),
      listener: _buildListener(),
    );
    await _banner?.load();
    return _banner!;
  }

  static AdRequest _getBannerAdRequest() {
    return AdRequest();
  }

  static BannerAdListener _buildListener() {
    return BannerAdListener(
      onAdOpened: (Ad ad) {
        print('${Constants.Tag} BannerAdListener onAdOpened ${ad.toString()}.');
      },
      onAdClosed: (Ad ad) {
        print('${Constants.Tag} BannerAdListener onAdClosed ${ad.toString()}.');
      },
      onAdImpression: (Ad ad) {
        print(
            '${Constants.Tag} BannerAdListener onAdImpression ${ad.toString()}.');
      },
      onAdWillDismissScreen: (Ad ad) {
        print(
            '${Constants.Tag} BannerAdListener onAdWillDismissScreen ${ad.toString()}.');
      },
      onPaidEvent: (
        Ad ad,
        double valueMicros,
        PrecisionType precision,
        String currencyCode,
      ) {
        print('${Constants.Tag} BannerAdListener PaidEvent ${ad.toString()}.');
      },
      onAdLoaded: (Ad ad) {
        print('${Constants.Tag} BannerAdListener onAdLoaded ${ad.toString()}.');
      },
      onAdFailedToLoad: (Ad bannerAd, LoadAdError error) {
        bannerAd.dispose();
        print(
            '${Constants.Tag} BannerAdListener onAdFailedToLoad error is ${error.responseInfo} | ${error.message} | ${error.code} | ${error.domain}');
      },
    );
  }

  static void disposeBanner() {
   _banner?.dispose();
  }
 }

Second step is in our UI will be our widget:

FutureBuilder<Widget>(
      future: Ads.buildBannerWidget(
        context: context,
      ),
      builder: (_, snapshot) {
        if (!snapshot.hasData)return Text("No Banner yet");

          return Container(
            height: 90,
            width: MediaQuery.of(context).size.width,
            child: snapshot.data,
          );
    
      },
    )

I tried all of these, but only the following worked for me. I added myBanner.dispose() to iniState to each of my pages.

void initState(){
  myBanner.dispose();
  myBanner.load();
  super.initState();
}

#Step-1: Make A Stateful Class Like Below:

import 'package:flutter/cupertino.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';

class BannerAdmob extends StatefulWidget{
  @override
  State<StatefulWidget> createState() {
    return _BannerAdmobState();
  }
}

class _BannerAdmobState extends State<BannerAdmob>{

  late BannerAd _bannerAd;
  bool _bannerReady = false;

  @override
  void initState() {
    super.initState();
    _bannerAd = BannerAd(
      adUnitId: "ca-app-pub-3940256099942544/6300978111",
      request: const AdRequest(),
      size: AdSize.largeBanner,
      listener: BannerAdListener(
        onAdLoaded: (_) {
          setState(() {
            _bannerReady = true;
          });
        },
        onAdFailedToLoad: (ad, err) {
          setState(() {
            _bannerReady = false;
          });
          ad.dispose();
        },
      ),
    );
    _bannerAd.load();
  }

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

  @override
  Widget build(BuildContext context) {
    return _bannerReady?SizedBox(
      width: _bannerAd.size.width.toDouble(),
      height: _bannerAd.size.height.toDouble(),
      child: AdWidget(ad: _bannerAd),
    ):Container();
  }
}

#Step-2: Use this as below:

@override
Widget build(BuildContext context){
  return BannerAdmob();
}

You have to use UniqueKey like this

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');
  }
 }
}

Adding a unique key to each SizedBox (or another widget) containing the AdWidget did solve my problem.

You have to initialize and load the ads in the initState() method.

And here is how I used the banner ad key as the widget's unique key.

if (_bannerAd1 != null)
        SizedBox(
          key: Key(_bannerAd1!.adUnitId),
          height: _bannerAd1?.size.height.toDouble(),
          width: _bannerAd1?.size.width.toDouble(),
          child: AdWidget(
            ad: _bannerAd1!,
          ),
        ),

if (_bannerAd2 != null)
        SizedBox(
          key: Key(_bannerAd2!.adUnitId),
          height: _bannerAd2?.size.height.toDouble(),
          width: _bannerAd2?.size.width.toDouble(),
          child: AdWidget(
            ad: _bannerAd2!,
          ),
        ),

Nothing above worked for me. In my case to show three adbanner in the same List I hade to create 3 independent variables to load 3 independent AdWidegt. See attached code to have an aproximation to resolve this problem. Note: resolved the error that we talks about, I have now an unresolved performance problem with banners when memory is deleted every time that banners disappears from the screen because scroll. This force rebuilds and disposings every time the scrolls occurs...

@override
  Widget build (BuildContext context) {
    final BannerAd? banner1 = _bannerAd1;
    final BannerAd? banner2 = _bannerAd2;
    final BannerAd? banner3 = _bannerAd3;
    
    if(widget.index==2 && _bannerAd1isAvailable && banner1 != null ){
      print(banner1.adUnitId);
      return AdContainer(banner: banner1, numBanner: 1,);
    }else if(widget.index==12 && _bannerAd2isAvailable && banner2 != null  ){
      print(banner2.adUnitId);
      return AdContainer(banner: banner2, numBanner: 2,);
    }else if(widget.index==22 &&  _bannerAd3isAvailable && banner3 != null ){
      print(banner3.adUnitId);
      return AdContainer(banner: banner3, numBanner: 3,);
    }else{
      return Container();
    }
 }
  
 class AdContainer extends StatelessWidget {
  const AdContainer({
    Key? key,
    required this.banner, required this.numBanner,
  }) : super(key: key);

  final int numBanner;
  final BannerAd banner;

  @override
  Widget build(BuildContext context) {
    final AdWidget object1 = AdWidget(ad: banner);
    final AdWidget object2 = AdWidget(ad: banner);
    final AdWidget object3 = AdWidget(ad: banner);
    return Container(
      width: double.infinity,
      color: const Color.fromRGBO(13, 37, 63, 0.6),
      padding: const EdgeInsets.symmetric(vertical: 20,horizontal: 20),
      child: Container(
        alignment: Alignment.center,
        height: banner.size.height.toDouble(),
        width: banner.size.width.toDouble(),
        child: (numBanner==1)?object1:(numBanner==2)?object2:object3,
   
      ),
    );
  }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM