On this page14 sections
  1. Prerequisites
  2. Connection With Previous Lectures
  3. Learning Objectives
  4. Lesson
  5. 1. The Helper
  6. 2. Watching the App Lifecycle
  7. 3. The 30-Second Rule
  8. Code Examples
  9. Common Beginner Mistakes
  10. Best Practices
  11. Mini Challenge
  12. Practice Questions
  13. Practice Answers
  14. Key Takeaways

Lecture 006 — App Open Ads & the AdState Flag

Category2 — Banner & Full-Screen Ads
Lecture006 of 010
Estimated video length18 min
TargetFlutter 3.44 · Dart 3.12 · google_mobile_ads 9
LevelBeginner
Source codeGitHub — urraan_admob_final

An app-open ad appears when the user comes back to the app. It is easy to annoy people with it, so we use a small shared flag that keeps it quiet for 30 seconds after any other full-screen ad.

Contents

Prerequisites · Connection · Objectives · Lesson · Code Examples · Common Mistakes · Best Practices · Mini Challenge · Practice Questions · Answers · Takeaways


Prerequisites

Lectures 003–005; WidgetsBindingObserver.


Connection With Previous Lectures

Lecture 005 introduced AdState. Now the app-open ad reads it.


Learning Objectives

By the end of this lecture you will be able to:

  • Load and show an AppOpenAd
  • Detect the app coming back with didChangeAppLifecycleState
  • Avoid showing two full-screen ads back to back
  • Reload after each show

Lesson

1. The Helper

dart
// lib/admob/app_open_ad_helper.dart
import 'package:flutter/foundation.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';

import 'admob_manager.dart';

class AppOpenAdHelper {
  AppOpenAd? _appOpenAd;
  bool _isAdShowing = false;

  void loadAppOpenAd() {
    AppOpenAd.load(
      adUnitId: AdmobManager.appOpenId,
      request: const AdRequest(),
      adLoadCallback: AppOpenAdLoadCallback(
        onAdLoaded: (ad) {
          _appOpenAd = ad;
          ad.fullScreenContentCallback = FullScreenContentCallback(
            onAdDismissedFullScreenContent: (ad) {
              ad.dispose();
              _appOpenAd = null;
              _isAdShowing = false;
              loadAppOpenAd();
            },
            onAdFailedToShowFullScreenContent: (ad, error) {
              ad.dispose();
              _appOpenAd = null;
              _isAdShowing = false;
            },
          );
        },
        onAdFailedToLoad: (error) {
          debugPrint('App open failed: ${error.code} ${error.message}');
          _appOpenAd = null;
        },
      ),
    );
  }

  void showAdIfAvailable() {
    if (_appOpenAd == null || _isAdShowing) {
      if (_appOpenAd == null) loadAppOpenAd();
      return;
    }
    _isAdShowing = true;
    _appOpenAd!.show();
  }

  void dispose() => _appOpenAd?.dispose();
}
  • showAdIfAvailable shows the ad if one is loaded. If none is loaded it starts loading one for next time.
  • _isAdShowing prevents the same ad being shown twice.

2. Watching the App Lifecycle

WidgetsBindingObserver tells a widget when the app goes to the background and returns:

dart
// lib/screen_dashboard.dart
import 'package:flutter/material.dart';

import 'admob/ad_state.dart';
import 'admob/app_open_ad_helper.dart';
import 'screen_banner_ads.dart';
import 'screen_interstitials.dart';
import 'screen_native_ads.dart';
import 'screen_rewarded_ads.dart';
import 'screen_rewarded_interstitial_ads.dart';

class ScreenDashboard extends StatefulWidget {
  const ScreenDashboard({super.key});

  @override
  State<ScreenDashboard> createState() => _ScreenDashboardState();
}

class _ScreenDashboardState extends State<ScreenDashboard>
    with WidgetsBindingObserver {
  final appOpenAdHelper = AppOpenAdHelper();

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
    appOpenAdHelper.loadAppOpenAd();
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    appOpenAdHelper.dispose();
    super.dispose();
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    super.didChangeAppLifecycleState(state);
    if (state == AppLifecycleState.resumed &&
        !AdState().isOtherAdsDisabled) {
      appOpenAdHelper.showAdIfAvailable();
    }
  }

  void _open(Widget screen) {
    Navigator.push(context, MaterialPageRoute(builder: (_) => screen));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Dashboard')),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            ElevatedButton(
              onPressed: () => _open(const ScreenBannerAds()),
              child: const Text('Banner Ads'),
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () => _open(const ScreenInterstitials()),
              child: const Text('Interstitial Ads'),
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () => _open(const ScreenNativeAds()),
              child: const Text('Native Ads'),
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () => _open(const ScreenRewardedAds()),
              child: const Text('Rewarded Ads'),
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () => _open(const ScreenRewardedInterstitialAds()),
              child: const Text('Rewarded Interstitial Ads'),
            ),
          ],
        ),
      ),
    );
  }
}
  • addObserver in initState, removeObserver in dispose.
  • When the state is resumed and AdState().isOtherAdsDisabled is false, we show the app-open ad.

3. The 30-Second Rule

Interstitial and rewarded ads disable app-open ads when they show and enable them 30 seconds after they close (enableAppOpenAdsAfterCooldown). Without this, closing a full-screen ad can bring the app "back", triggering an app-open ad immediately after.


Code Examples

Example 1 — Test It

Run the app, press the Home button, then reopen the app. The test app-open ad appears. Open an interstitial screen, finish the ad, and background/reopen within 30 seconds — no app-open ad should show.


Common Beginner Mistakes

1. Never removing the observer — the dashboard keeps receiving events after it is gone.

2. Showing the ad over the splash screen — the user has not seen your app yet. This demo shows it only when the app comes back (resumed).

3. No cooldown — two full-screen ads back to back.


Best Practices

  • Do not show an app-open ad right after another full-screen ad
  • Do not show it before the user has seen your app's first screen
  • Always remove observers in dispose

Mini Challenge

Change the cooldown from 30 to 60 seconds in AdState and explain who calls it.


Practice Questions

  1. Which lifecycle state triggers the ad?
  2. Why does AdState exist?
  3. What does _isAdShowing prevent?
  4. When do we remove the observer?

Practice Answers

  1. AppLifecycleState.resumed.
  2. To stop two full-screen ads appearing together.
  3. Showing the same ad twice.
  4. In dispose.

Key Takeaways

  • App open = lifecycle resumed + available ad
  • AdState coordinates full-screen ads
  • Add and remove the observer