flutter 这个AdWidget已经在Widget树中了,如何禁用这个异常,它意味着什么?

kqqjbcuj  于 2022-12-30  发布在  Flutter
关注(0)|答案(9)|浏览(135)

所以我在列表中插入了admob广告。我在列表视图中添加了无限滚动功能。所以当用户滚动到列表末尾时,新的项目会添加到列表中。有了这些项目,我也会在列表中添加admob广告。
所以当用户滚动到最后,新的项目和广告被添加到列表中。这时,下面的异常被捕获。那么如何解决这个异常。

======== 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)

代码:-

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

dldeef671#

除了Kafil Khan的answer之外,还可以使用StatefulBuilder Package Container小部件。
示例:

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

vdgimpew2#

问题是你一次又一次地放同一个小部件。你可以通过创建一个新的StatefulWidget类并返回Adwidget来修复这个问题,这将多次构建同一个小部件,它的工作原理就像一个Builder。这解决了我的问题,希望它也能为你工作!:)
你也不必为一个广告单元提供多个id。

wqnecbli

wqnecbli3#

Step-1:创建一个有状态的类,如下所示:

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

步骤-2:按如下所示使用:

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

5kgi1eie4#

当您要添加新横幅时,必须为其分配一个新ID:

BannerAd(adUnitId: 'somethingDifferentThanTheOneInTheTree')

正如错误日志中明确指出的:
如果您将此AdWidget放在列表中,请确保在生成器函数中使用唯一的广告对象创建一个新示例。请确保未在多个AdWidget中使用同一广告对象。

mrphzbgm

mrphzbgm5#

我尝试了所有这些方法,但只有以下方法对我有效:我在每个页面上添加了myBanner.dispose()iniState

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

prdp8dxp6#

就像上面的answer一样,我将把我的实现放在这里,让未来的新手更容易。
首先,让我们创建一个名为ad_helper的helper类来隐藏UI中的所有内容,它包含两个helper方法:

第一个方法是buildBannerWidget,这是一个公共方法,用于根据需要构建广告小工具。
第二个方法是_instantiateBanner,这是一个私有方法,用于在每次调用buildBannerWidget方法时构建bannerAd对象。

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

第二步是在我们的用户界面将是我们的小部件:

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

vxbzzdmp7#

您必须像这样使用UniqueKey

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

ujv3wf0j8#

以上没有任何工作对我来说。**在我的情况下,显示三个广告在同一个列表中,我不得不创建3个独立的变量来加载3个独立的AdWidegt。见所附的代码有一个近似值来解决这个问题。**注意:解决了我们讨论的错误,我现在有一个未解决的性能问题,横幅时,内存被删除,每次横幅从屏幕上消失,因为滚动。这股力量重建和处置每次滚动发生...

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

55ooxyrt9#

向每个包含AdWidgetSizedBox(或其他小部件)添加唯一的key确实解决了我的问题。
您必须在initState()方法中初始化并加载广告。
下面是我如何使用横幅广告键作为小部件的唯一键。

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

相关问题