Recycle banner ads in scrollable feeds

In long or infinite scrolling lists, such as news feeds or social feeds, creating a banner ad for every item position can lead to high memory consumption and UI lag. For improved performance, use banner recycling to maintain a small, fixed-size cache of BannerAd instances and reuse off-screen ads as the user scrolls.

This page covers sizing an ad cache, checking whether a BannerAd instance is off-screen, and re-assigning cached ads to list positions.

The following image shows how an off-screen banner ad returns to the cache and is reused in a list position as the user scrolls:

Diagram showing a banner ad scrolling off-screen into the ad cache and reused at the bottom of the on-screen feed.

Prerequisites

Before you continue, use inline adaptive for scrolling banners.

Track assigned list positions

As your user scrolls, banner recycling keeps a limited number of BannerAd instances in memory and moves these instances between list positions.

To know which ad to reuse, track which list position each ad in the cache occupies.

Maintain the following state in your widget:

  • A fixed-size cache of BannerAd instances. The size you set the cache to is the maximum number of ads held in memory at once.
  • An ad interval that determines how frequently an ad appears in the list.
  • A position Map class that associates each BannerAd instance with the list position the instance occupies. When you recycle an ad, update the entry in this Map class to point to the latest position.
  • A size Map class that stores the platform-resolved AdSize object for each loaded ad. After the ad loads, inline adaptive banner heights are known.

The following example tracks the assigned list positions for each ad in the cache:

// Maximum number of banner ad instances kept in the cache.
static const int _cacheSize = 8;

// Spacing between banner ads. For example, index 0, 6, 12, and so on
// display ads.
static const int _adInterval = 6;

// The cache of allocated BannerAd instances.
final List<BannerAd> _banners = [];

// Maps each BannerAd instance to its currently assigned list item position.
final Map<BannerAd, int> _bannerPositions = {};

// Stores the platform-resolved size of each loaded banner ad.
final Map<BannerAd, AdSize> _bannerSizes = {};

Choose a cache size

Make the cache larger than the maximum number of ads that can appear on screen at the same time. If the cache is smaller than that size, the ad selected for recycling is often still visible, and your app allocates additional ads instead of reusing the ads.

To choose a size, do the following:

  1. Estimate how many ads fit on screen at once by dividing the viewport height by the height of one ad interval.
  2. Add space for ads that are still detaching during fast scrolls.
  3. If you see that ads selected for recycling are still on screen, increase the cache size.

The example on this page displays an ad every six rows and uses a cache of eight. This example leaves space for the few ads visible on screen at once.

Check whether an ad is on screen

All Google Mobile Ads Flutter Plugin classes that render platform views implement the AdWithView class and provide an isMounted getter. Before re-assigning a cached ad to a list position, check the isMounted getter to confirm the ad has scrolled off-screen and is safe to reuse.

Retrieve and recycle a banner ad

When the list requests a banner for a given position, do the following:

  1. Reuse the ad already assigned to that position, if one exists.
  2. If the cache isn't full, request an ad.
  3. If the cache is full, select a cached ad and verify that the isMounted getter is the false value before re-assigning the ad.
  4. Call the dispose() method on every ad in the cache when the widget is removed from the widget tree.

The following example retrieves and recycles a banner ad:

BannerAd _getRecycledBannerAd(BuildContext context, int bannerPosition) {
  // 1. If an ad is already mapped to this position, reuse it.
  final BannerAd? existingBanner = _bannerPositions.entries
      .firstWhereOrNull((entry) => entry.value == bannerPosition)
      ?.key;
  if (existingBanner != null) {
    return existingBanner;
  }

  // 2. If the cache is not full, allocate a new ad instance.
  if (_banners.length < _cacheSize) {
    final BannerAd newBanner = _createBannerAd(context);
    _banners.add(newBanner);
    _bannerPositions[newBanner] = bannerPosition;
    return newBanner;
  }

  // 3. Select an existing ad from the cache using modulo arithmetic.
  final BannerAd targetBanner = _banners[bannerPosition % _cacheSize];

  // 4. Verify the ad is detached from the screen before recycling.
  if (targetBanner.isMounted) {
    // If still on screen during fast scrolls, allocate a temporary instance.
    return _createBannerAd(context);
  } else {
    // Safe to reuse: reassign to the new position.
    _bannerPositions[targetBanner] = bannerPosition;
    return targetBanner;
  }
}

For the complete example, see Google Mobile Ads Flutter Plugin example.