Harsh Patel
Harsh Patel

Reputation: 269

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

Upvotes: 13

Views: 15576

Answers (9)

Omar Fayad
Omar Fayad

Reputation: 1793

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!,
          ),
        ),

Upvotes: 2

ricardo_dev
ricardo_dev

Reputation: 51

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

Upvotes: 0

Muhammad Issa Sabbagh
Muhammad Issa Sabbagh

Reputation: 87

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

Upvotes: 1

Md Sirajul Islam Sojib
Md Sirajul Islam Sojib

Reputation: 111

#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();
}

Upvotes: 9

Huthaifa Muayyad
Huthaifa Muayyad

Reputation: 12343

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.

Upvotes: 4

Sanka Werapitiya
Sanka Werapitiya

Reputation: 127

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

Upvotes: 3

Nabil alhafez
Nabil alhafez

Reputation: 448

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

Upvotes: 3

iqfareez
iqfareez

Reputation: 887

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

Upvotes: 18

Kafil khan
Kafil khan

Reputation: 111

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.

Upvotes: 8

Related Questions