当调用setState时,Flutter Admob小部件出现错误。

5

我在Admob的小部件上遇到了问题。

我正在为一个Flutter应用程序开发一个新功能,其中包含一个Admob横幅小部件。但是当我设置另一个小部件的值时,Admob小部件会出现错误。

我正在使用:google_mobile_ads: ^0.11.0+1

横幅的构建方式如下:

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

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

小部件的显示效果如下:

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

日志错误捕获:

    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


1
请问您能否添加错误日志?似乎有一些信息但无法在gif上进行阅读。 - jignesh.world
1
请问您使用的是哪个广告包?在 pub.dev 上有很多选择 (https://pub.dev/packages?q=admob)。 - galloper
1
我也收到了相同的错误信息。 - Shrijan Regmi
嘿,你好。你能修复这个问题吗?我在这里也遇到了同样的问题:https://dev59.com/H8Lra4cB1Zd3GeqPGUwH,但是无论在这里还是其他地方都找不到解决方案。如果你找到了,能否请你稍微告诉一下我呢?因为这个问题是我发布应用程序之前的最后一步! - kalfalarin_omer
4个回答

2
根据 Google 广告套餐示例:

googleads-mobile-flutter

你必须使用 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');
    }
   }
  }

1
不错的发现,这应该是被采纳的答案。 - giorgio79

0

调用 setState() 会导致小部件重新构建,因此您的 FutureBuilder() 小部件会再次触发未来任务。这就是为什么您会看到这个错误。

您需要删除 FutureBuilder() 并将未来任务移动到 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

你也可以使用布尔值来控制在未来任务完成时是否显示AdWidget()

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

0

从问题和错误日志来看,似乎你正在尝试使用setState更新屏幕/小部件而不是实际的基于广告的容器小部件-但仍然出现此错误。

如果我理解正确...那么问题在于广告小部件正在尝试使用旧的广告对象在setState调用上重新构建,但它每次新构建时都期望新的广告对象。因此,避免这些类型的小部件构建,如果不需要的话。

创建一个单独的基于广告的容器小部件,例如AppXyzAdWidget,并将广告的父容器代码和其他广告相关代码放置在新小部件中,并在屏幕上使用新创建的基于广告的小部件。

这样,您就可以将屏幕与广告相关的内容分离开来,然后在setState之后,将不会重新加载您的广告及其小部件,只会刷新您的内容。


-1
你能否在build函数中移除initState代码并像这样初始化_adBanner?
@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();
        });
  }

请勿在构建方法中放置任何加载内容。 - Nimr Sawafta
@NimrSawafta _adBanner.load() 是一个 Future,它在 FutureBuilder 中。在 FutureBuilder 中加载 Future 有什么问题吗? - Shrijan Regmi
是的,每当您调用AdWidget时(它本身就是Future),对于您的代码,每次调用build方法都会重新构建广告,导致帧率下降,并可能被Google暂停。 - Nimr Sawafta
@NimrSawafta 错误提示为 => “此 AdWidget 已经在 Widget 树中。如果您将此 AdWidget 放置在列表中,请确保在 builder 函数中创建一个新实例,并使用唯一的广告对象”。因此,为了解决这个问题,每次都需要加载广告。然而,admob 包已经更新,处理了这个问题。 - Shrijan Regmi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接