Last edited: 2025/05/29
Functions:
AdListener
– Monitors responses from the ad server.Methods:
onAdFailedToLoad()
– Triggered when the ad request fails; initiates the UR process.removeAdFromLayout()
– Removes the failed ad view from the layout.createSecondAd()
– Builds and loads a new ad slot with custom targeting.addAdToLayout()
– Injects the new ad view into the layout.AdListener
SetupAttach 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)
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)
}
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)
}
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)
}
"Publisher_ad_unit_ID"
and "Unfilled_Recovery_ad_unit_ID"
with actual ad unit paths:
"Publisher_ad_unit_ID"
is the ad unit path of the Ad unit where Unfilled Recovery will work"Unfilled_Recovery_ad_unit_ID"
is the ad unit path of Unfilled Recovery Ad unit - you will receive it from YieldbirdConstraintLayout
with the ID main_layout
.Classes & Methods:
AdListener
– Monitors responses from the ad server.onAdFailedToLoad()
– Triggered when the ad request fails; initiates the UR process.removeAdFromLayout()
– Removes the failed ad view from the layout.createSecondAd()
– Builds and loads a new ad slot with custom targeting.addAdToLayout()
– Injects the new ad view into the layout.AdListener
SetupAttach 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);
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);
}
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);
}
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);
}
"Publisher_ad_unit_ID"
and "Unfilled_Recovery_ad_unit_ID"
with actual ad unit paths:
"Publisher_ad_unit_ID"
is the ad unit path of the Ad unit where Unfilled Recovery will work."Unfilled_Recovery_ad_unit_ID"
is the ad unit path of the Unfilled Recovery Ad unit – you will receive it from Yieldbird.ConstraintLayout
with the ID main_layout
.Install Google Mobile Ads SDK - use CocoaPods or Swift Package Manager:
CocoaPods:
will trigger the Unfilled Recovery.
pod 'Google-Mobile-Ads-SDK'
Then run:
pod 'Google-Mobile-Ads-SDK'
Swift Package Manager (Xcode 12+):
Classes/Functions:
GADBannerView
– main ad containerGADBannerViewDelegate
– delegate for ad lifecycle eventsbannerView(_:didFailToReceiveAdWithError:)
– handles ad failurecreateSecondAd(publisherAdUnitId:)
– rebuilds ad with recovery unitremoveAdFromLayout(adView:)
– removes the failed adaddAdToLayout(adView:)
– inject new adUnfilled Recovery logic:
bannerView(_:didFailToReceiveAdWithError:)
is triggered, remove the old ad and load a new one with Unfilled_Recovery_ad_unit_ID
.yb_ur_adunitpath
should be set with the original publisher's ad unit ID.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 ?? "")
}
}
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")
}
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
}
}
}
Publisher_ad_unit_ID
and Unfilled_Recovery_ad_unit_ID
with real values.Publisher_ad_unit_ID
- is the ID of the current publisher entity to which we want to implement Unfilled RecoveryUnfilled_Recovery_ad_unit_ID
- this is the ID of the unit provided by Yieldbird as part of the Unfilled Recovery solution