Android + iOS Integration

Dynamic Price is a set of utilities and helper methods for including the Nimbus auction in an existing Google Ad Manager implementation.

Build Setup

Follow the example below to include the Nimbus SDK Google extension which contains the helper methods for Dynamic Price. You should have already completed the Android or iOS Integration guide to initialize the SDK before proceeding.

build.gradle.kts
dependencies {
    implementation("com.adsbynimbus.android:extension-google:2.+")
}

Implementation

Nimbus Dynamic Price integrates with the Google Mobile Ads SDK by appending key-value parameters from a Nimbus ad to the Ad Manager request and listening for a win notification from the Google ad object to render the Nimbus ad.

Define a LinearPriceMapping

The LinearPriceMapping tells Nimbus how to map winning bid values to line items that have been setup in the Ad Manager account. The mapping is a collection of ordered LinearPriceGranularity objects which define how to bucket the bids for each part of the bid range. The following code implementation matches the Ad Manager Setup Price Granularities.

Please consult your Ad Ops team or your Nimbus Account Manager to obtain the correct values for your setup.

val priceMapping = LinearPriceMapping(
    // $0.00 - $5.00, 1 cent increments
    LinearPriceGranularity(0, 500, 1), 
    // $5.00 - $10.00, 10 cent increments
    LinearPriceGranularity(500, 1000, 10),
    // $10.00 - $50.00, 50 cent increments
    LinearPriceGranularity(1000, 5000, 50),
)

Add the handleEventForNimbus method to an AppEventListener/Delegate

Dynamic Price uses the App Event functionality provided by the Google Ads SDK to receive a notification if the Nimbus ad should be served. Using the extension methods provided on the Banner or Interstitial objects, call handleEventForNimbus(name, info) in the app event callback to notify the Nimbus SDK of the event and determine if Nimbus should render.

handleEventForNimbus should be the first method called in the app event callback and can be added to an existing listener / delegate; it will return true if the Nimbus ad was selected to be rendered.

Request a Nimbus Ad and applyDynamicPrice

The NimbusAd.applyDynamicPrice method will setup the Nimbus ad to be served using Ad Manager as the mediation. To request an ad from Nimbus follow the steps below; additional information on ad requests can be found in the Requesting section (Android / iOS). Code examples can be found at the end of this page.

The first parameter of each NimbusRequest helper method identifies where in the application the ad is being served and is visible on the Nimbus dashboard for reporting. It must not contain any auto-incrementing or randomly generated values.

  1. Create a NimbusRequest using one of the provided helper methods. The request to Nimbus should match the type of Ad Manager ad that it will be loaded into.

    • forBannerAd -> AdManagerAdView / GAMBannerView / AdLoader

    • forInterstitialAd -> AdManagerInterstitialAd / GAMInterstitialAd

    • forRewardedVideoAd -> RewardedAd / GADRewardedAd

  2. Send the request to Nimbus using the NimbusAdManager or NimbusRequestManager objects and wait for the success or failure callback.

  3. If an ad is returned from Nimbus, call applyDynamicPrice with the AdManagerAdRequest.Builder / GAMRequest object to setup Dynamic Price using the LinearPriceMapping defined earlier. This will append the required key-values to the Google request.

As a reminder, if implementing Dynamic Price alongside other Ad Networks, it is required that all requests to each network are called at the same time. Responses from all networks including Nimbus must be received prior to appending the key-value pairs from each response into the GAM request.

Proceed with Google Load

Once the Nimbus auction has completed and all key-values have been appended, the request will be ready to send to Google. Ensure that all listeners and delegates have been set on the Google ad object and proceed to load the ad.

Setting Delegates on iOS

iOS provides an additional applyDynamicPrice extension method to set the Google delegate on the GAMBannerView and GAMInterstitial objects and should be used in all cases. It requires a NimbusRequestManager instance and an optional NimbusAd object for interstitials only.

// Banners
bannerView.applyDynamicPrice(requestManager: nimbusRequestManager, delegate: self)

// Interstitials
interstitialAd.applyDynamicPrice(
    ad: ad, 
    requestManager: nimbusRequestManager,
    delegate: self)

Setting a listener or delegate on the Google ad object after the ad has loaded will interfere with proper functionality of Dynamic Price and should be avoided.

Load the Ad

// Banners
adManagerAdView.load(adManagerAdRequest)

// AdLoader
adManagerAdLoader.loadAd(adManagerAdRequest)

// Interstitials
AdManagerInterstitialAd.load(requireContext(), adUnit, adManagerAdRequest, loadCallback)

// Rewarded
RewardedAd.load(requireActivity(), rewardedUnitId, adManagerRequest, loadCallback)

Proper functionality of Dynamic Price requires the use of the applyDynamicPrice, loadDynamicPrice, and presentDynamicPrice extension methods on the GAMBannerView and GAMInterstitial objects and should be called for every ad load. If Nimbus does not return an ad, pass ad: nil for each function or omit the parameter.

Code Examples

Fully functional Dynamic Price examples are available in the nimbus-android-sample and nimbus-ios-sample repositories.

lateinit var nimbusAdManager: NimbusAdManager
lateinit var googleAdView: AdManagerAdView
lateinit var nimbusRequest: NimbusRequest

/* Helper property to determine if the request did not fill */
inline val LoadAdError.isNoFill: Boolean
    get() = code in intArrayOf(AdRequest.ERROR_CODE_NO_FILL, AdRequest.ERROR_CODE_MEDIATION_NO_FILL)

/**
 * Helper function to create and set the Listeners required for Dynamic Price rendering.
 *
 * If using another implementation of AdListener, AppEventListener, onPaidEventListener the
 * following four methods must be modified to send signals to the Nimbus SDK. 
 * @param auctionData Created after receiving a response from Nimbus
 */
fun AdManagerAdView.setNimbusListeners(auctionData: GoogleAuctionData) {
    val listener = object : AdListener(), AppEventListener, OnPaidEventListener {
        /**
         * If the ad fails to load with no fill, notify the NimbusAdManager.
         * No Fill = AdRequest.ERROR_CODE_NO_FILL or AdRequest.ERROR_CODE_MEDIATION_NO_FILL
         */
        override fun onAdFailedToLoad(loadError: LoadAdError) {
            if (loadError.isNoFill) adManager.notifyNoFill(auctionData)
        }
        
        /** When an impression occurs, notify the NimbusAdManager with the responseInfo */
        override fun onAdImpression() {
            adManager.notifyImpression(auctionData, responseInfo)
        }
        
        /** When pricing information is available, call onPaidEvent on auctionData */
        override fun onPaidEvent(event: AdValue) {
            auctionData.onPaidEvent(event)
        }
        
        /** 
         * This method is called when the AdManagerAdView loads the creative.
         * 
         * Call handleEventForNimbus before any other code to ensure it receives the event.
         * 
         * If handleEventForNimbus returns true, set nimbusWin on auctionData */
         */
        override fun onAppEvent(name: String, info: String) {
            if (handleEventForNimbus(name, info)) auctionData.nimbusWin = true
        }
    }
    
    /* Set the listeners on the instance of the AdManagerAdView */
    adListener = listener
    appEventListener = listener
    onPaidEventListener = listener
}

/* Dynamic Price request and rendering flow */
lifecycleScope.launch {
    val adManagerRequest = AdManagerAdRequest.Builder()
    runCatching {
        adManager.makeRequest(root.context, nimbusRequest)
    }.onSuccess { nimbusAd ->
        /* Create and store a GoogleAuctionData instance for use with this ad load */
        val auctionData = GoogleAuctionData(nimbusAd)
        
        /* Ensure the Nimbus callbacks are included in the AdManagerAdView */
        adManagerAdView.setNimbusListeners(auctionData)
        
        /* Call apply Dynamic Price on the AdManagerRequest */
        adManagerRequest.applyDynamicPrice(nimbusAd, mapping = priceMapping)
        
        /* Send the adManagerRequest to Google */
        adManagerAdView.loadAd(adManagerRequest.build())
    }
}

AdLoader

lateinit var nimbusAdManager: NimbusAdManager
lateinit var googleAdView: AdManagerAdView
lateinit var nimbusRequest: NimbusRequest

/* Helper property to determine if the request did not fill */
inline val LoadAdError.isNoFill: Boolean
    get() = code in intArrayOf(AdRequest.ERROR_CODE_NO_FILL, AdRequest.ERROR_CODE_MEDIATION_NO_FILL)

/**
 * Helper function to create and set the Listeners required for Dynamic Price rendering.
 *
 * If using another implementation of AdListener the
 * following four methods must be modified to send signals to the Nimbus SDK. 
 * @param auctionData Created after receiving a response from Nimbus
 */
 class AdLoaderAdListener(
    val auctionData: GoogleAuctionData,
    val adManager: NimbusAdManager,
) : AdListener() {
    var responseInfo : ResponseInfo? = null

    /**
     * If the ad fails to load with no fill, notify the NimbusAdManager.
     * No Fill = AdRequest.ERROR_CODE_NO_FILL or AdRequest.ERROR_CODE_MEDIATION_NO_FILL
     */        
    override fun onAdFailedToLoad(loadError: LoadAdError) {
        if (loadError.isNoFill) adManager.notifyNoFill(auctionData)
    }

    /** When an impression occurs, notify the NimbusAdManager with the responseInfo */
    override fun onAdImpression() {
        adManager.notifyImpression(auctionData, responseInfo)
    }
}

/* Dynamic Price request and rendering flow */
lifecycleScope.launch {
    runCatching {
        adManager.makeRequest(root.context, forBannerAd("<position>", Format.MREC))   
     }.onSuccess { nimbusAd ->
        val adManagerRequest = AdManagerAdRequest.Builder()     
        /* Create and store a GoogleAuctionData instance for use with this ad load */
        val auctionData = GoogleAuctionData(nimbusAd)
        /* Call apply Dynamic Price on the AdManagerRequest */
        adManagerRequest.applyDynamicPrice(nimbusAd, mapping = priceMapping)
        /* Create an instance of the listener above */
        val adLoaderAdListener = AdLoaderAdListener(auctionData, nimbusAdManager)
        
        googleAdLoader = AdLoader.Builder(root.context.applicationContext, unitId).forAdManagerAdView({
            /* Adjust the layout params as needed */
            it.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
            
            /** 
             * This method is called when the AdManagerAdView loads the creative.
             * Call handleEventForNimbus before any other code to ensure it receives the event.
             * If handleEventForNimbus returns true, set nimbusWin on auctionData */
             */
            it.setAppEventListener { name, info ->
                if (it.handleEventForNimbus(name, info)) auctionData.nimbusWin = true
            }
            
            /** When pricing information is available, call onPaidEvent on auctionData */
            it.setOnPaidEventListener { event ->
                auctionData.onPaidEvent(event)
            }
            
            /* AdLoaderAdListener needs responseInfo to track impressions */
            adLoaderAdListener.responseInfo = it.responseInfo
            
            /* Add adView to the container view. */
            view.addView(it)
        }, AdSize.MEDIUM_RECTANGLE).withAdListener(adLoaderAdListener).build()
        
        /* Send the adManagerRequest to Google */
        googleAdLoader.loadAd(adManagerRequest)
    }
}

Interstitials

lateinit var nimbusAdManager: NimbusAdManager
lateinit var nimbusRequest: NimbusRequest

/* Helper property to determine if the request did not fill */
inline val LoadAdError.isNoFill: Boolean
    get() = code in intArrayOf(AdRequest.ERROR_CODE_NO_FILL, AdRequest.ERROR_CODE_MEDIATION_NO_FILL)

/**
 * Helper function to create and set the Listeners required for Dynamic Price rendering.
 *
 * If using another implementation of AdListener, AppEventListener, onPaidEventListener the
 * following four methods must be modified to send signals to the Nimbus SDK. 
 * @param auctionData Created after receiving a response from Nimbus
 */
 fun AdManagerInterstitialAd.setNimbusListeners(auctionData: GoogleAuctionData) {
    val eventListener = object : FullScreenContentCallback(), AppEventListener, OnPaidEventListener {
        
        /** When an impression occurs, notify the NimbusAdManager with the responseInfo */
        override fun onAdImpression() {
            adManager.notifyImpression(auctionData, responseInfo)
        }
        
        /** 
         * This method is called when the AdManagerAdView loads the creative.
         * 
         * Call handleEventForNimbus before any other code to ensure it receives the event.
         * 
         * If handleEventForNimbus returns true, set nimbusWin on auctionData */
         */
        override fun onAppEvent(name: String, info: String) {
            if (handleEventForNimbus(name, info)) auctionData.nimbusWin = true
        }
         /** When pricing information is available, call onPaidEvent on auctionData */
        override fun onPaidEvent(p0: AdValue) {
            auctionData.onPaidEvent(p0)
        }
    }
     /* Set the listeners on the instance of the AdManagerInterstitialAd */
    appEventListener = eventListener
    fullScreenContentCallback = eventListener
    onPaidEventListener = eventListener
}

/* Dynamic Price request and rendering flow */
lifecycleScope.launch {
    runCatching {
        adManager.makeRequest(root.context, nimbusRequest)
    }.onSuccess { nimbusAd ->
        /* Create and store a GoogleAuctionData instance for use with this ad load */
        val auctionData = GoogleAuctionData(nimbusAd)
        /* Call applyDynamicPrice on the AdManagerRequest */
        val adManagerRequest = AdManagerAdRequest.Builder()
        .applyDynamicPrice(nimbusAd, mapping = priceMapping)
        .build()
        AdManagerInterstitialAd.load(requireContext(), unitId,
            adManagerRequest, object : AdManagerInterstitialAdLoadCallback() {
                override fun onAdLoaded(ad: AdManagerInterstitialAd) {
                    
                    /* Ensure the Nimbus callbacks are included in the AdManagerInterstitialAd */
                    ad.setNimbusListeners(auctionData)
                    
                    /* Display the interstitial */
                    ad.show(requireActivity())
                 }
        })
    }
}

Rewarded Video

import com.adsbynimbus.google.*

lateinit var nimbusAdManager: NimbusAdManager
var nimbusRequest: NimbusRequest = NimbusRequest.forRewardedVideo(position = )

object ExampleRewardCallback : NimbusRewardCallback {
    override fun onAdImpression() { }
    override fun onAdClicked() { }
    override fun onAdPresented() { }
    override fun onAdClosed() { }
    override fun onUserEarnedReward(rewardItem: RewardItem) { }
    override fun onError(nimbusError: NimbusError) { }
}

lifecycleScope.launch {
    runCatching {
        adManager.makeRequest(root.context, nimbusRequest)
    }.onSuccess { nimbusAd ->
        val adManagerRequest = AdManagerAdRequest.Builder()
            .applyDynamicPrice(nimbusAd, mapping = priceMapping)
            .build()
        RewardedAd.load(requireActivity(), rewardedUnitId,
            adManagerRequest, object : RewardedAdLoadCallback() {
            override fun onAdLoaded(ad: RewardedAd) {
                
                /* When ready to show the ad, use */
                ad.showAd(requireActivity(), nimbusAd, adManager, ExampleRewardCallback)
            }
        })
    }
}

Last updated