Skip to main content

Overview

Backfill ads automatically display alternative ads when direct ads are unavailable, maximizing revenue. Adrop supports major ad networks such as AdMob and Pangle as backfill ads.
Additional platform-specific setup is required to use backfill ads.

Android Setup

1. Gradle Configuration

settings.gradle.kts

Add the Pangle ad network repository:
settings.gradle.kts
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
        maven { url = uri("https://artifact.bytedance.com/repository/pangle") }
    }
}

build.gradle.kts

Add backfill ad dependencies:
dependencies {
    implementation("io.adrop:adrop-ads:1.7.2")
    implementation("io.adrop:adrop-ads-backfill:1.7.2")
}

2. AndroidManifest.xml Configuration

If using AdMob as backfill ads, add the AdMob App ID to AndroidManifest.xml:
AndroidManifest.xml
<manifest>
    <application>
        <!-- AdMob App ID -->
        <meta-data
            android:name="com.google.android.gms.ads.APPLICATION_ID"
            android:value="ca-app-pub-xxxxxxxxxxxxxxxx~yyyyyyyyyy"/>
    </application>
</manifest>
Replace ca-app-pub-xxxxxxxxxxxxxxxx~yyyyyyyyyy with your actual AdMob App ID.

3. ProGuard Configuration

If using ProGuard, add the following rules:
proguard-rules.pro
-keep class io.adrop.** { *; }
-dontwarn io.adrop.**

iOS Setup

1. Modify Podfile

Add backfill ad dependencies to your Podfile:
Podfile
target 'Runner' do
  use_frameworks!
  use_modular_headers!

  # Adrop SDK
  pod 'adrop-ads'

  # Backfill Ads SDK
  pod 'adrop-ads-backfill'

  flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
end
After adding dependencies, run the following command:
cd ios && pod install --repo-update && cd ..

2. Info.plist Configuration

If using AdMob as backfill ads, add the AdMob App ID to Info.plist:
Info.plist
<key>GADApplicationIdentifier</key>
<string>ca-app-pub-xxxxxxxxxxxxxxxx~yyyyyyyyyy</string>
Replace ca-app-pub-xxxxxxxxxxxxxxxx~yyyyyyyyyy with your actual AdMob App ID.

Console Configuration

Enable backfill ads in the Adrop Console:
  1. Log in to AdControl Console
  2. Navigate to Ad Units menu
  3. Select the ad unit to use backfill ads
  4. Enable backfill ads in the Backfill Settings section
  5. Select the backfill ad network to use (AdMob, Pangle, etc.)
  6. Enter ad network-specific settings (e.g., AdMob Ad Unit ID)

Using in Flutter

Checking Backfill Ads

Use the isBackfilled property to check if an ad is a backfill ad.

Native Ads

For native ads, you need to render the media view differently based on whether it’s a backfill ad.
import 'package:flutter/material.dart';
import 'package:adrop_ads_flutter/adrop_ads_flutter.dart';
import 'package:webview_flutter/webview_flutter.dart';

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

  @override
  State<NativeAdWithBackfill> createState() => _NativeAdWithBackfillState();
}

class _NativeAdWithBackfillState extends State<NativeAdWithBackfill> {
  bool isLoaded = false;
  AdropNativeAd? nativeAd;
  late final WebViewController webViewController;

  @override
  void initState() {
    super.initState();

    // Initialize WebView controller
    webViewController = WebViewController()
      ..setJavaScriptMode(JavaScriptMode.unrestricted);

    _createNativeAd();
  }

  void _createNativeAd() {
    nativeAd = AdropNativeAd(
      unitId: 'YOUR_UNIT_ID',
      useCustomClick: true,
      listener: AdropNativeListener(
        onAdReceived: (ad) {
          debugPrint('Ad received: ${ad.isBackfilled ? "backfill" : "direct"}');

          // Load HTML creative in WebView for direct ads
          if (!ad.isBackfilled && ad.properties.creative != null) {
            webViewController.loadHtmlString(ad.properties.creative!);
          }

          setState(() {
            isLoaded = true;
          });
        },
        onAdFailedToReceive: (ad, errorCode) {
          if (errorCode == AdropErrorCode.backfillNoFill) {
            debugPrint('No backfill ads available');
          } else {
            debugPrint('Ad failed to load: $errorCode');
          }
        },
      ),
    );

    nativeAd?.load();
  }

  @override
  Widget build(BuildContext context) {
    if (!isLoaded) return const SizedBox.shrink();

    return AdropNativeAdView(
      ad: nativeAd,
      child: Container(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // Headline
            if (nativeAd?.properties.headline != null)
              Text(
                nativeAd!.properties.headline!,
                style: const TextStyle(
                  fontSize: 18,
                  fontWeight: FontWeight.bold,
                ),
              ),
            const SizedBox(height: 8),

            // Media view - render differently based on backfill status
            _buildMediaView(),

            // CTA button
            if (nativeAd?.properties.callToAction != null)
              Padding(
                padding: const EdgeInsets.only(top: 8),
                child: ElevatedButton(
                  onPressed: () {},
                  child: Text(nativeAd!.properties.callToAction!),
                ),
              ),
          ],
        ),
      ),
    );
  }

  Widget _buildMediaView() {
    if (nativeAd?.isBackfilled == true) {
      // Backfill ad: use image asset
      return Image.network(
        nativeAd?.properties.asset ?? '',
        width: double.infinity,
        height: 200,
        fit: BoxFit.cover,
        loadingBuilder: (context, child, loadingProgress) {
          if (loadingProgress == null) return child;
          return const SizedBox(
            height: 200,
            child: Center(child: CircularProgressIndicator()),
          );
        },
        errorBuilder: (context, error, stackTrace) {
          return const SizedBox(
            height: 200,
            child: Center(child: Icon(Icons.error)),
          );
        },
      );
    } else if (nativeAd?.properties.creative != null) {
      // Direct ad: render HTML creative with WebView
      return SizedBox(
        width: double.infinity,
        height: 300,
        child: WebViewWidget(controller: webViewController),
      );
    } else {
      return const SizedBox.shrink();
    }
  }
}
For native ads that are not backfill ads, video ads must be rendered directly using WebView.
For banner ads, you can check backfill status through metadata.
bannerView = AdropBannerView(
  unitId: 'YOUR_UNIT_ID',
  listener: AdropBannerListener(
    onAdReceived: (unitId, metadata) {
      final isBackfilled = metadata?['isBackfilled'] ?? false;
      if (isBackfilled == true) {
        debugPrint('Backfill ad loaded');
      } else {
        debugPrint('Direct ad loaded');
      }
    },
    onAdFailedToReceive: (unitId, errorCode) {
      if (errorCode == AdropErrorCode.backfillNoFill) {
        debugPrint('No backfill ads available');
      }
    },
  ),
);

Interstitial/Rewarded/Popup Ads

You can check backfill status similarly for interstitial, rewarded, and popup ads.
interstitialAd = AdropInterstitialAd(
  unitId: 'YOUR_UNIT_ID',
  listener: AdropInterstitialListener(
    onAdReceived: (ad) {
      debugPrint('Ad loaded');
      // Interstitial ads don't have a separate isBackfilled property,
      // but backfill ads are automatically loaded if backfill is configured in the console
    },
    onAdFailedToReceive: (ad, errorCode) {
      if (errorCode == AdropErrorCode.backfillNoFill) {
        debugPrint('No direct or backfill ads available');
      }
    },
  ),
);

Ad Serving Flow

Backfill ads are served in the following order:

Error Codes

Error codes related to backfill ads.
Error CodeDescription
adNoFillCannot receive direct ads (will attempt backfill)
backfillNoFillCannot receive backfill ads either
onAdFailedToReceive: (ad, errorCode) {
  switch (errorCode) {
    case AdropErrorCode.adNoFill:
      debugPrint('No direct ads. Attempting backfill.');
      break;
    case AdropErrorCode.backfillNoFill:
      debugPrint('No backfill ads available.');
      break;
    default:
      debugPrint('Ad load failed: ${errorCode.code}');
  }
}

Supported Ad Formats

Backfill ads support the following formats:
Ad FormatSupportDescription
BannerSupportedFixed-size banner ads
NativeSupportedCustomizable native ads
InterstitialSupportedFull-screen ads
RewardedSupportedRewarded ads
PopupSupportedPopup-style ads

Best Practices

Enable Backfill Ads

Enable backfill ads for all ad units to maximize ad fill rate and revenue.

Set Appropriate Timeouts

Set appropriate timeouts for direct and backfill ads to improve user experience.

Analyze Backfill Ads

Track and analyze the ratio of direct to backfill ads using the isBackfilled property.

Handle Native Ad Media

For native ads, use Image.network or WebView appropriately based on backfill status.

Important Notes

  • Android: You must add the io.adrop:adrop-ads-backfill:1.7.2 dependency to use backfill ads.
  • iOS: You must add pod 'adrop-ads-backfill' to your Podfile to use backfill ads.
  • When using AdMob, you must add the AdMob App ID to each platform’s manifest file.
  • For native ads that are not backfill ads, you must render media with WebView.
  • You must comply with the policies of backfill ad networks.

Next Steps