Unfilled Recovery inApp implementation

Last edited: 2025/05/29


Android: Kotlin

Key Components

Functions:

Methods:

Implementation Details

1. AdListener Setup

Attach an AdListener to the existing ad request. If the GAM’s response is empty, the listener will trigger the Unfilled Recovery.

adView.adListener = object: AdListener() {
   override fun onAdFailedToLoad(adError : LoadAdError) {
       removeAdFromLayout(adView)
       adView.destroy()
       createSecondAd(publisherAdUnitId)
   }
}
adView.loadAd(adRequest)

2. Creating an Unfilled Recovery Ad unit

If AdListener detects an empty response from GAM, createSecondAd() generates a new (Unfilled Recovery) ad request. createSecondAd() method takes a String containing the publisher's ad unit path as a parameter.

private fun createSecondAd(publisherAdUnitId: String) {
   val adView = AdManagerAdView(this)
   adView.setAdSize(AdSize.BANNER)
   adView.adUnitId = "Unfilled_Recovery_ad_unit_ID"
   addAdToLayout(adView)
   val adRequest: AdManagerAdRequest = AdManagerAdRequest.Builder()
       .addCustomTargeting("yb_ur_adunitpath", publisherAdUnitId)
       .build()
   adView.loadAd(adRequest)
}

3. Layout Handling

Ensure the original Ad unit is removed before injecting a new one (injecting Unfiled Recovery ad):

private fun removeAdFromLayout(adView: AdManagerAdView) {
   val constraintLayout: ConstraintLayout = findViewById(R.id.main_layout)
   constraintLayout.removeViewInLayout(adView)

To insert a new ad:

private fun addAdToLayout(adView: AdManagerAdView) {
   adView.id = View.generateViewId()
   val constraintLayout: ConstraintLayout = findViewById(R.id.main_layout)
   constraintLayout.addView(adView)
   val set = ConstraintSet()
   set.clone(constraintLayout)
   set.connect(adView.id, ConstraintSet.RIGHT, ConstraintSet.PARENT_ID, ConstraintSet.RIGHT)
   set.connect(adView.id, ConstraintSet.LEFT, ConstraintSet.PARENT_ID, ConstraintSet.LEFT)
   set.applyTo(constraintLayout)
}

Full Implementation Example

private fun createFirstAd() {
   val publisherAdUnitId = "Publisher_ad_unit_ID"
   val adView = AdManagerAdView(this)
   adView.setAdSize(AdSize.BANNER)
   adView.adUnitId = publisherAdUnitId
   addAdToLayout(adView)
   val adRequest = AdManagerAdRequest.Builder().build()
   adView.adListener = object: AdListener() {
       override fun onAdFailedToLoad(adError : LoadAdError) {
           removeAdFromLayout(adView)
           adView.destroy()
           createSecondAd(publisherAdUnitId)
       }
   }
   adView.loadAd(adRequest)
}

private fun createSecondAd(publisherAdUnitId: String) {
   val adView = AdManagerAdView(this)
   adView.setAdSize(AdSize.BANNER)
   adView.adUnitId = "Unfilled_Recovery_ad_unit_ID"
   addAdToLayout(adView)
   val adRequest: AdManagerAdRequest = AdManagerAdRequest.Builder()
       .addCustomTargeting("yb_ur_adunitpath", publisherAdUnitId)
       .build()
   adView.loadAd(adRequest)
}

private fun addAdToLayout(adView: AdManagerAdView) {
   adView.id = View.generateViewId()
   val constraintLayout: ConstraintLayout = findViewById(R.id.main_layout)
   constraintLayout.addView(adView)
   val set = ConstraintSet()
   set.clone(constraintLayout)
   set.connect(adView.id, ConstraintSet.RIGHT, ConstraintSet.PARENT_ID, ConstraintSet.RIGHT)
   set.connect(adView.id, ConstraintSet.LEFT, ConstraintSet.PARENT_ID, ConstraintSet.LEFT)
   set.applyTo(constraintLayout)
}

private fun removeAdFromLayout(adView: AdManagerAdView) {
   val constraintLayout: ConstraintLayout = findViewById(R.id.main_layout)
   constraintLayout.removeViewInLayout(adView)
}

Notes on Integration


Android: Java

Key Components

Classes & Methods:

Implementation Details

1. AdListener Setup

Attach an AdListener to the existing ad request. If the GAM’s response is empty, the listener will trigger the Unfilled Recovery.

adView.setAdListener(new AdListener() {
    @Override
    public void onAdFailedToLoad(LoadAdError adError) {
        removeAdFromLayout(adView);
        adView.destroy();
        createSecondAd(publisherAdUnitId);
    }
});
adView.loadAd(adRequest);

2. Creating an Unfilled Recovery Ad unit

If AdListener detects an empty response from GAM, createSecondAd() generates a new (Unfilled Recovery) ad request. createSecondAd() method takes a String containing the publisher's ad unit path as a parameter.

private void createSecondAd(String publisherAdUnitId) {
    AdManagerAdView adView = new AdManagerAdView(this);
    adView.setAdSize(AdSize.BANNER);
    adView.setAdUnitId("Unfilled_Recovery_ad_unit_ID");
    addAdToLayout(adView);

    AdManagerAdRequest adRequest = new AdManagerAdRequest.Builder()
        .addCustomTargeting("yb_ur_adunitpath", publisherAdUnitId)
        .build();

    adView.loadAd(adRequest);
}

3. Layout Handling

Ensure the original Ad unit is removed before injecting a new one (Unfilled Recovery ad).

To remove an ad:

private void removeAdFromLayout(AdManagerAdView adView) {
    ConstraintLayout constraintLayout = findViewById(R.id.main_layout);
    constraintLayout.removeViewInLayout(adView);
}

To insert a new ad:

private void addAdToLayout(AdManagerAdView adView) {
    adView.setId(View.generateViewId());
    ConstraintLayout constraintLayout = findViewById(R.id.main_layout);
    constraintLayout.addView(adView);

    ConstraintSet set = new ConstraintSet();
    set.clone(constraintLayout);
    set.connect(adView.getId(), ConstraintSet.RIGHT, ConstraintSet.PARENT_ID, ConstraintSet.RIGHT);
    set.connect(adView.getId(), ConstraintSet.LEFT, ConstraintSet.PARENT_ID, ConstraintSet.LEFT);
    set.applyTo(constraintLayout);
}

Full Implementation Example

private void createFirstAd() {
    final String publisherAdUnitId = "Publisher_ad_unit_ID";
    final AdManagerAdView adView = new AdManagerAdView(this);
    adView.setAdSize(AdSize.BANNER);
    adView.setAdUnitId(publisherAdUnitId);
    addAdToLayout(adView);

    AdManagerAdRequest adRequest = new AdManagerAdRequest.Builder().build();

    adView.setAdListener(new AdListener() {
        @Override
        public void onAdFailedToLoad(LoadAdError adError) {
            removeAdFromLayout(adView);
            adView.destroy();
            createSecondAd(publisherAdUnitId);
        }
    });

    adView.loadAd(adRequest);
}

private void createSecondAd(String publisherAdUnitId) {
    AdManagerAdView adView = new AdManagerAdView(this);
    adView.setAdSize(AdSize.BANNER);
    adView.setAdUnitId("Unfilled_Recovery_ad_unit_ID");
    addAdToLayout(adView);

    AdManagerAdRequest adRequest = new AdManagerAdRequest.Builder()
        .addCustomTargeting("yb_ur_adunitpath", publisherAdUnitId)
        .build();

    adView.loadAd(adRequest);
}

private void addAdToLayout(AdManagerAdView adView) {
    adView.setId(View.generateViewId());
    ConstraintLayout constraintLayout = findViewById(R.id.main_layout);
    constraintLayout.addView(adView);

    ConstraintSet set = new ConstraintSet();
    set.clone(constraintLayout);
    set.connect(adView.getId(), ConstraintSet.RIGHT, ConstraintSet.PARENT_ID, ConstraintSet.RIGHT);
    set.connect(adView.getId(), ConstraintSet.LEFT, ConstraintSet.PARENT_ID, ConstraintSet.LEFT);
    set.applyTo(constraintLayout);
}

private void removeAdFromLayout(AdManagerAdView adView) {
    ConstraintLayout constraintLayout = findViewById(R.id.main_layout);
    constraintLayout.removeViewInLayout(adView);
}

Notes on Integration


iOS / Swift

Required Libraries

Install Google Mobile Ads SDK - use CocoaPods or Swift Package Manager:

  1. CocoaPods:

    will trigger the Unfilled Recovery.

    pod 'Google-Mobile-Ads-SDK'
    

    Then run:

    pod 'Google-Mobile-Ads-SDK'
    
  2. Swift Package Manager (Xcode 12+):

Key Components

Classes/Functions:

Unfilled Recovery logic:

Implementation Details

UIKit Version (Full Example)

import UIKit
import GoogleMobileAds

class ViewController: UIViewController, GADBannerViewDelegate {

    var adView: GAMBannerView?

    override func viewDidLoad() {
        super.viewDidLoad()
        createFirstAd()
    }

    func createFirstAd() {
        let publisherAdUnitId = "Publisher_ad_unit_ID"
        let adView = GAMBannerView(adSize: kGADAdSizeBanner)
        adView.adUnitID = publisherAdUnitId
        adView.rootViewController = self
        adView.delegate = self
        addAdToLayout(adView)
        adView.load(GAMRequest())
        self.adView = adView
    }

    func createSecondAd(publisherAdUnitId: String) {
        let adView = GAMBannerView(adSize: kGADAdSizeBanner)
        adView.adUnitID = "Unfilled_Recovery_ad_unit_ID"
        adView.rootViewController = self
        let request = GAMRequest()
        request.customTargeting = ["yb_ur_adunitpath": publisherAdUnitId]
        addAdToLayout(adView)
        adView.load(request)
        self.adView = adView
    }

    func addAdToLayout(_ adView: GAMBannerView) {
        adView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(adView)
        NSLayoutConstraint.activate([
            adView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
            adView.centerXAnchor.constraint(equalTo: view.centerXAnchor)
        ])
    }

    func removeAdFromLayout(_ adView: GAMBannerView) {
        adView.removeFromSuperview()
    }

    func bannerView(_ bannerView: GADBannerView, didFailToReceiveAdWithError error: Error) {
        print("Ad failed to load: \\(error.localizedDescription)")
        removeAdFromLayout(bannerView)
        createSecondAd(publisherAdUnitId: bannerView.adUnitID ?? "")
    }
}

SwiftUI Version (with UIViewRepresentable)

import SwiftUI
import GoogleMobileAds

struct GAMBannerAdView: UIViewRepresentable {

    let adUnitId: String
    let isRecovery: Bool
    let originalAdUnitId: String?

    func makeUIView(context: Context) -> GAMBannerView {
        let banner = GAMBannerView(adSize: kGADAdSizeBanner)
        banner.adUnitID = adUnitId
        banner.rootViewController = UIApplication.shared.windows.first?.rootViewController
        banner.delegate = context.coordinator

        let request = GAMRequest()
        if isRecovery, let original = originalAdUnitId {
            request.customTargeting = ["yb_ur_adunitpath": original]
        }
        banner.load(request)
        return banner
    }

    func updateUIView(_ uiView: GAMBannerView, context: Context) {}

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    class Coordinator: NSObject, GADBannerViewDelegate {
        var parent: GAMBannerAdView

        init(_ parent: GAMBannerAdView) {
            self.parent = parent
        }

        func bannerView(_ bannerView: GADBannerView, didFailToReceiveAdWithError error: Error) {
            print("SwiftUI Ad failed: \\(error.localizedDescription)")
            DispatchQueue.main.async {
                NotificationCenter.default.post(
                    name: .unfilledRecovery,
                    object: parent.originalAdUnitId
                )
            }
        }
    }
}

extension Notification.Name {
    static let unfilledRecovery = Notification.Name("UnfilledRecovery")
}

Usage in SwiftUIView

struct ContentView: View {
    @State private var isRecoveryMode = false

    var body: some View {
        VStack {
            Spacer()
            if isRecoveryMode {
                GAMBannerAdView(adUnitId: "Unfilled_Recovery_ad_unit_ID", isRecovery: true, originalAdUnitId: "Publisher_ad_unit_ID")
                    .frame(height: 50)
            } else {
                GAMBannerAdView(adUnitId: "Publisher_ad_unit_ID", isRecovery: false, originalAdUnitId: nil)
                    .frame(height: 50)
            }
        }
        .onReceive(NotificationCenter.default.publisher(for: .unfilledRecovery)) { notification in
            isRecoveryMode = true
        }
    }
}

Notes on Integration