0

I've put in a github issue but just wanted to check with the community that this sounds right to everyone else.

Logically when creating and "ad" and an "adwidget" in flutter they may rebuild with the parent widgets, however, they may "logically" be the same ad shown in the same spot within the app (identical pixels even).

However it appears that the tracking for if an ad is already shown is rudimentary and doesn't account for duplication during rebuilds and then throws errors.

My example below will work where you pass in a provider or state handled ad into any widget that gets rebuilt by it's parent (future builder, navigator pops, provider watches, etc).

From here there is no real problem. But remove the Future.delayed and now as the rebuild has a temporary non-utilized object in the tree potentially while native platform views are being unmounted etc, we get the error thrown. Throw in the Future.delayed again and now with this rudimentary number being applied we wait for the previous widget to be fully cleaned up and then place this widget exactly where it was and no issue.

I just want to confirm that this sounds about right to be a github issue for them? To me it would make sense that their handling accounts for re-use in the same location rather than this type of handling? Or at least provide a callback for dispose of previous so we can knowingly say, yes this isn't a duplicate is a rebuild and we're willing to push it back in after disposal of parents.

So you can go through yourself just create a static ad with the test admob id, a stateful widget with a setstate on button press, then place the following widget into it, test both with and without the Future.delayed:

flutter doctor > 
[✓] Flutter (Channel master, 3.38.0-1.0.pre-400, on macOS 26.0.1 25A362 darwin-arm64, locale en-AU)
[✓] Android toolchain - develop for Android devices (Android SDK version 35.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 26.1)
[✓] Chrome - develop for the web
[✓] Connected device (3 available)
[✓] Network resources

Widget:

import "package:flutter/material.dart"; import "package:flutter/scheduler.dart"; import "package:google_mobile_ads/google_mobile_ads.dart"; import "package:hookd/data/providers/explore_provider.dart"; import "package:provider/provider.dart";
enum AdState { idle, loading, loaded, error }

class AdViewMain extends StatefulWidget {
final bool dismissable;
final Ad? advertisement;

const AdViewMain({
required this.advertisement,
this.dismissable = false,
super.key,
});

@OverRide
State createState() => _AdViewMainState();
}

class _AdViewMainState extends State {
bool isLoading = true;
Ad? ad;

@OverRide
void initState() {
WidgetsBinding.instance.addPostFrameCallback((_) async {
setState(() => ad = widget.advertisement);
});
super.initState();
}

@OverRide
Widget build(BuildContext context) {
if (widget.advertisement == null) return _buildErrorPlaceholder(context);
if (ad == null) return _buildLoadingSkeleton(context);

return Stack(
  alignment: Alignment.topRight,
  children: [
    _buildAdWidget(context),
    if (widget.dismissable)
      GestureDetector(
        onTap: () async {
          // Mark dismissed and coordinate safe disposal
          context.read<ExploreProvider>().removeAd();
        },
        child: Padding(
          padding: const EdgeInsets.only(top: 8, right: 8),
          child: Container(
            padding: const EdgeInsets.all(6),
            margin: const EdgeInsets.all(6),
            decoration: BoxDecoration(
              color: Theme.of(context).colorScheme.surfaceContainer,
              shape: BoxShape.circle,
            ),
            child: const Icon(
              Icons.close,
              color: Colors.white,
              size: 20,
            ),
          ),
        ),
      ),
  ],
);
}

Widget _buildAdWidget(BuildContext context) {
return ClipRRect(
borderRadius: BorderRadius.circular(12),
child: SizedBox(
child: AdWidget(ad: ad as BannerAd),
),
);
}

Widget _buildLoadingSkeleton(BuildContext context) {
return Container(
width: double.infinity,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainer,
borderRadius: BorderRadius.circular(24),
),
child: Center(
child: SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation(
Theme.of(context).colorScheme.primary.withOpacity(0.6),
),
),
),
),
);
}

Widget _buildErrorPlaceholder(BuildContext context) {
return Container(
width: double.infinity,
height: 60,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.errorContainer.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: Theme.of(context).colorScheme.outline.withOpacity(0.2),
width: 1,
),
),
child: Center(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.error_outline,
size: 16,
color: Theme.of(context).colorScheme.onSurfaceVariant.withOpacity(0.5),
),
const SizedBox(width: 8),
Text(
"Unable to load advertisement",
style: TextStyle(
fontSize: 12,
color: Theme.of(context).colorScheme.onSurfaceVariant.withOpacity(0.7),
fontWeight: FontWeight.w500,
),
),
],
),
),
);
}
}
6
  • 1
    What's the actual question here? Please edit your question to specifically and clearly define the problem that you are trying to solve. Additional details — including all relevant code, error messages, and debugging logs — will help readers to better understand your problem and what you are asking. Commented Nov 6 at 6:35
  • Question was clear, I want to confirm with community if these seems like a reasonable expectation. I don't need a code snippet, just confirmation that this is good programming expectation from plugin developers. OR my expectations are wrong and the code should be entirely different Commented Nov 6 at 7:01
  • 1
    It's not clear at all - If you actual question is "I've created a github issue - Is this correct?", then your question is off-topic. Furthermore you are stating that "during rebuilds and then throws errors", but you didn't include any errors. What's the point of posting this question? Commented Nov 6 at 7:05
  • If you don't understand the topic matter for the error listed in the title, you shouln't be commenting. The sentence "I just want to confirm that this sounds about right to be a github issue for them? " In the actual question is beyond clear. It also clearly invites solutions where a github issue is not the solution to a coding problem. If you skimmed the title without knowing about admob's errors, or didn't read the question, please don't vote or comment until you have a good grasp Commented Nov 6 at 7:11
  • Rather that just adding the error inside the title, please add the full error with stacktrace inside the question. Also please see How do I write a good title? Commented Nov 6 at 7:18

2 Answers 2

0

Not sure about your question here but if you are altering the shape of an ad you should use Native Ads not Banners.

In addition to avoid any errors you should change the ad logic one level of hierarchy higher so the adView is as dumb as possible and dont duplicate the ad state and handle it separatly.

Sign up to request clarification or add additional context in comments.

Comments

0

For anyone facing the issue and unsure what to do ->

Digging further up the tree I tried to find what's causing 2 temporary adwidgets at the same time was an animation controller that could be "cancelled" which mean it had adwidget a -> other widget b -> adwidget c

As animation wasn't completed to widget b but was being told to switch back to the ad widget, it was trying to show c and a at the same time. Which is "normal" behaviour.

I put in an issue on github and it appears the underlying Platformview doesn't allow 2 instances at all in flutter. I suggested in the future trying to add better handling for if child of "animation" etc where it may handle alternate frames, or may try to simply show a placeholder (last render, grey box, whatever really) for 1 widget and the other get's a platform view. However it's low priorty.

The solution as is written above for now, is simply ensuring you delay your widget to ad attachment until all animations in your app are completed, which may mean decoupling / random waits, leave comments to show where they came from but it can't be helped.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.