Trusted Arbiter
Compare CloudX bids with supported third-party bids in Android apps
Trusted Arbiter compares a loaded CloudX bid with supported third-party bids and returns the selected platform. CloudX SDK versions 4.1.0 and later support CloudX, Unity LevelPlay, and PubMatic bid inputs. CloudX SDK 4.2.0 and later also supports custom publisher-supplied bid inputs.
Supported ad formats
Trusted Arbiter is format-agnostic: it takes any loaded CloudX ad and compares it against the supplied third-party bids, regardless of ad format. Banner, MREC, Interstitial, and Rewarded are all supported.
- Interstitial and Rewarded (fullscreen formats) follow the step-by-step walkthrough and controller pattern shown later on this page.
- Banner and MREC (view formats) need the additional handling covered in Banner and MREC arbitration below, since a losing bid’s view must never be attached to the view hierarchy.
Basic API
Create bid candidates from loaded ads, then pass them to CloudX.arbiter().
// cloudXAd is the CloudXAd from a CloudX onAdLoaded callback.
// levelPlayAdInfo is the Unity LevelPlay ad info object.
// pobBid is the PubMatic/OpenWrap bid object.
val bids = listOf(
CloudXArbiterBid.cloudX(cloudXAd),
CloudXArbiterBid.levelPlay(
networkName = levelPlayAdInfo.adNetwork,
revenue = levelPlayAdInfo.revenue,
precision = levelPlayAdInfo.precision,
),
CloudXArbiterBid.pubmatic(
price = pobBid.price,
partnerName = pobBid.partnerName,
)
)
val configuration = CloudXArbiterConfiguration.builder(bids).build()
CloudX.arbiter(configuration, object : CloudXArbiterListener {
override fun onCompleted(result: CloudXArbiterResult) {
Log.d("CloudX", "Selected platform: ${result.platform.name}")
}
})CloudXArbiterBid.cloudX() accepts the CloudXAd object from a CloudX load callback. levelPlay() accepts Unity LevelPlay ad info values. pubmatic() accepts a PubMatic OpenWrap bid price and optional partner name. partnerName and the extras map are optional on the bid factories. onCompleted() runs on the main thread, so you can show an ad or update UI directly from it.
result.platform is CloudXArbiterPlatform.CLOUDX, LEVELPLAY, or PUBMATIC for the selected platform, or CloudXArbiterPlatform.NONE when no winner could be selected (for example, no bids were supplied).
AdMob and Google Ad Manager
CloudX compares a loaded CloudX ad with a loaded AdMob or Google Ad Manager ad. AdMob and Ad Manager are separate demand sources, so both may bid in the same arbitration. This applies to publisher-managed mediation setups, including accounts described commercially as AdMob Pro; there is no separate AdMob Pro SDK API.
Google demand does not normally reveal the price of a loaded ad before it is shown, so there is no pre-bid price for the arbiter to compare against CloudX’s bid. CloudX estimates the bid price from the prior performance of similar ad units, so you do not need to supply a price. No pre-bid pricing API is required.
If your AdMob account exposes impression-level revenue data pre-bid, you can supply that exact price yourself instead of using the estimate — see Manual input with pre-bid ILRD below.
Report Google paid events back to CloudX (required)
Forwarding Google’s paid events is a required part of the Trusted Arbiter AdMob and Ad Manager integration, not an optional analytics extra. After you show an AdMob or Ad Manager ad that won an arbitration, hand Google’s impression-level revenue for that ad to the CloudX SDK. Without it, CloudX never learns what Google demand actually paid, and the estimates it produces for future arbitrations degrade.
Google delivers this revenue per ad through OnPaidEventListener — there is no global impression bus — so attach the listener to every arbitrated Google ad and forward each event through CloudX.reportRevenueData(). AdValue.valueMicros is in micro-units of the reported currency, so divide by 1_000_000.0 to get revenue for one impression. Use CloudXRevenuePlatform.ADMOB for AdMob ads and CloudXRevenuePlatform.GAM for Ad Manager ads.
private fun Int.toCloudXRevenuePrecision(): CloudXRevenuePrecision = when (this) {
AdValue.PrecisionType.PRECISE -> CloudXRevenuePrecision.EXACT
AdValue.PrecisionType.ESTIMATED -> CloudXRevenuePrecision.ESTIMATED
AdValue.PrecisionType.PUBLISHER_PROVIDED -> CloudXRevenuePrecision.PUBLISHER_DEFINED
else -> CloudXRevenuePrecision.UNDEFINED
}
private fun reportGooglePaidEvent(
platform: CloudXRevenuePlatform,
adValue: AdValue,
adFormat: String,
adUnitId: String,
responseInfo: ResponseInfo?,
): Boolean {
val servedBy = responseInfo?.loadedAdapterResponseInfo
return CloudX.reportRevenueData(
CloudXRevenueData.builder(
platform = platform,
revenue = adValue.valueMicros / 1_000_000.0,
adFormat = adFormat,
)
.currencyCode(adValue.currencyCode)
.precision(adValue.precisionType.toCloudXRevenuePrecision())
.networkName(servedBy?.adSourceName)
.adUnitId(adUnitId)
.thirdPartyAdPlacementId(servedBy?.adSourceInstanceName)
.build(),
)
}
// Attach the listener to the arbitrated Google ad before you show it.
adMobInterstitial.setOnPaidEventListener { adValue ->
reportGooglePaidEvent(
platform = CloudXRevenuePlatform.ADMOB,
adValue = adValue,
adFormat = "interstitial",
adUnitId = adMobAdUnitId,
responseInfo = adMobInterstitial.responseInfo,
)
}
// An Ad Manager ad reports the same way, with the GAM platform.
adManagerInterstitial.setOnPaidEventListener { adValue ->
reportGooglePaidEvent(
platform = CloudXRevenuePlatform.GAM,
adValue = adValue,
adFormat = "interstitial",
adUnitId = adManagerAdUnitId,
responseInfo = adManagerInterstitial.responseInfo,
)
}reportRevenueData() returns true when the event is accepted into the CloudX revenue pipeline, and false if the SDK is not initialized or server-side revenue tracking is disabled. For banner and MREC, attach the listener once to the AdView — Google re-fires paid events on the same view — and pass "banner" or "mrec" as the format. See Publisher-Reported Revenue Data for the full field reference and the other supported platforms.
Create the bid from the ad unit id of the ad you loaded:
// networkName is optional; pass the winning ad source when you know it, e.g.
// responseInfo?.loadedAdapterResponseInfo?.adSourceName.
val adMobBid = CloudXArbiterBid.adMob(
adUnitId = adMobAdUnitId,
networkName = adMobNetworkName ?: "admob",
manualRevenuePerImpressionUSD = null,
extras = emptyMap(),
)
// An Ad Manager ad unit id takes the form /NNNNNNN/placement/name.
val adManagerBid = CloudXArbiterBid.gam(
adUnitId = "/21775744923/example/interstitial",
networkName = "gam",
manualRevenuePerImpressionUSD = null,
extras = emptyMap(),
)
val configuration = CloudXArbiterConfiguration.builder(
listOf(CloudXArbiterBid.cloudX(cloudXAd), adMobBid, adManagerBid)
).build()
CloudX.arbiter(configuration, object : CloudXArbiterListener {
override fun onCompleted(result: CloudXArbiterResult) {
when (result.platform) {
CloudXArbiterPlatform.CLOUDX -> cloudXInterstitial.show(activity)
CloudXArbiterPlatform.ADMOB -> adMobInterstitial.show(activity)
CloudXArbiterPlatform.GAM -> adManagerInterstitial.show(activity)
else -> {}
}
}
})A winning Google bid reports its own platform — CloudXArbiterPlatform.ADMOB or CloudXArbiterPlatform.GAM. You no longer need to inspect result.platformName to tell these sources apart, as you would for a custom bid.
Pass a blank ad unit id and the bid still builds rather than crashing your app, but it carries no usable identity: it is never priced and the server rejects it.
Manual input with pre-bid ILRD
Some AdMob accounts expose impression-level revenue data pre-bid: the ad value for the loaded ad is available at load time, before the ad is shown. This is a legacy, account-gated capability, so check with your Google account team whether it is enabled for your account. An exact per-impression price that you know before show is the one case where supplying your own price beats CloudX’s estimate.
Pass the pre-bid ad value as manualRevenuePerImpressionUSD and it overrides the estimate:
val adMobBid = CloudXArbiterBid.adMob(
adUnitId = adMobAdUnitId,
networkName = adMobNetworkName ?: "admob",
// valueMicros is in micros: 1,000,000 micros is one currency unit.
// Do not also divide by 1,000 — this is already a per-impression value, not a CPM.
manualRevenuePerImpressionUSD = preBidAdValue.valueMicros / 1_000_000.0,
extras = emptyMap(),
)How the value is treated:
0.0is a real price. It means this bid is worth nothing — not that the price is missing.- Negative and non-finite values are not prices, so they are treated as absent and logged.
- A blank ad unit id drops the manual price entirely, because a bid with no identity cannot be validated.
manualRevenuePerImpressionUSD is revenue for a single impression, in USD — not CPM. Convert whatever your source reports:
- A Google ad value is reported in micros on Android, so divide by 1,000,000. A
valueMicrosof5000is0.005per impression. Do not confuse a micros value with an eCPM and divide by 1,000 as well. - A non-USD amount must be converted to USD first.
Step-by-step: arbitrate CloudX and LevelPlay
This walkthrough shows exactly which Unity LevelPlay callback to read and which values to pass into CloudX.arbiter(). It uses an interstitial, but the same field mapping applies to any format — see Supported ad formats for what changes for Banner and MREC.
Load both candidates
Create the CloudX and LevelPlay interstitials, attach listeners, and start a load on each platform.
val cloudXInterstitial = CloudX.createInterstitial(context, "YOUR_CLOUDX_AD_UNIT_ID")
cloudXInterstitial.listener = cloudXListener
cloudXInterstitial.load()
val levelPlayInterstitial = LevelPlayInterstitialAd("YOUR_LEVELPLAY_AD_UNIT_ID")
levelPlayInterstitial.setListener(levelPlayListener)
levelPlayInterstitial.loadAd()Capture each platform's loaded ad
LevelPlay delivers a LevelPlayAdInfo in its onAdLoaded callback; CloudX delivers a CloudXAd in onAdLoaded. Hold onto both — you read the arbiter inputs from them in the next step.
private var cloudXAd: CloudXAd? = null
private var levelPlayInfo: LevelPlayAdInfo? = null
// CloudXInterstitialListener
override fun onAdLoaded(cloudXAd: CloudXAd) {
this.cloudXAd = cloudXAd
}
// LevelPlayInterstitialAdListener
override fun onAdLoaded(levelPlayAdInfo: LevelPlayAdInfo) {
levelPlayInfo = levelPlayAdInfo
}Map the values into bids
Read the LevelPlay fields off LevelPlayAdInfo and pass them to CloudXArbiterBid.levelPlay(). The CloudX bid takes the CloudXAd directly. listOfNotNull submits only the platforms that actually loaded.
LevelPlayAdInfo field | Type | CloudXArbiterBid.levelPlay parameter |
|---|---|---|
adNetwork | String | networkName |
revenue | Double | revenue |
precision | String | precision |
val bids = listOfNotNull(
cloudXAd?.let { CloudXArbiterBid.cloudX(it) },
levelPlayInfo?.let { info ->
CloudXArbiterBid.levelPlay(
networkName = info.adNetwork, // LevelPlayAdInfo.adNetwork
revenue = info.revenue, // LevelPlayAdInfo.revenue
precision = info.precision, // LevelPlayAdInfo.precision
)
},
)Run the arbiter
Wrap the bids in a CloudXArbiterConfiguration and pass it to CloudX.arbiter() with a listener. onCompleted() runs on the main thread.
val configuration = CloudXArbiterConfiguration.builder(bids).build()
CloudX.arbiter(configuration, object : CloudXArbiterListener {
override fun onCompleted(result: CloudXArbiterResult) {
showWinner(result)
}
})Show the winner
Switch on result.platform and show the winning platform’s ad. CloudXArbiterPlatform.NONE means no winner was selected — continue without showing an ad.
private fun showWinner(result: CloudXArbiterResult) {
when (result.platform) {
CloudXArbiterPlatform.CLOUDX -> cloudXInterstitial.show(activity)
CloudXArbiterPlatform.LEVELPLAY -> levelPlayInterstitial.showAd(activity)
else -> { } // CloudXArbiterPlatform.NONE — no winner; continue without an ad
}
}The ArbiterInterstitialController below packages these same steps into a reusable component that prepares a winner ahead of the placement.
Interstitial example
This interstitial example arbitrates between two platforms: CloudX and Unity LevelPlay. Prepare a winner before the placement is reached:
- Load CloudX and LevelPlay in parallel.
- Wait until both platforms have loaded or failed.
- Submit only loaded candidates to Trusted Arbiter.
- Cache the selected platform.
- At the placement, show the cached winner immediately.
If both platforms fail, start another load cycle. If the placement is reached before a winner is prepared, continue the app flow without showing an ad.
/**
* Prepares a Trusted Arbiter winner ahead of time so an interstitial can be shown
* instantly when a placement is reached.
*
* Loads [cloudXInterstitial] and [levelPlayInterstitial] in parallel, waits until both
* have finished loading or failing, submits the loaded candidates to [CloudX.arbiter],
* and caches the selected [CloudXArbiterPlatform] in [nextWinner].
*/
class ArbiterInterstitialController(
private val cloudXInterstitial: CloudXInterstitialAd,
private val levelPlayInterstitial: LevelPlayInterstitialAd,
) {
/** Receives the arbitration outcome once a winner has been prepared. */
interface Listener {
/** Called when the arbiter has selected [platform] for the next show. */
fun onWinnerPrepared(platform: CloudXArbiterPlatform)
}
/** Set to observe [Listener.onWinnerPrepared] callbacks. */
var listener: Listener? = null
private var cloudXAd: CloudXAd? = null
private var cloudXLoadDone = false
private var levelPlayAdInfo: LevelPlayAdInfo? = null
private var levelPlayLoadDone = false
private var nextWinner: CloudXArbiterPlatform? = null
init {
cloudXInterstitial.listener = createCloudXListener()
levelPlayInterstitial.setListener(createLevelPlayListener())
}
/** Starts a load for each platform that does not currently hold a cached ad. */
fun loadMissingAds() {
if (cloudXAd == null) cloudXInterstitial.load()
if (levelPlayAdInfo == null) levelPlayInterstitial.loadAd()
}
/**
* Shows the prepared winner on [activity], returning true only when a show call was made.
*
* Returns false when no winner is ready or the cached ad is no longer available, in which
* case a fresh load cycle is started.
*/
fun showAtPlacement(activity: Activity): Boolean {
return when (nextWinner) {
CloudXArbiterPlatform.CLOUDX -> showCloudX(activity)
CloudXArbiterPlatform.LEVELPLAY -> showLevelPlay(activity)
else -> false
}
}
/**
* Runs the arbiter once both platforms have settled, then caches the winning platform.
*
* Returns early until both loads complete. If neither platform loaded, it restarts the
* load cycle; otherwise it submits the loaded candidates to [CloudX.arbiter].
*/
private fun maybePrepareWinner() {
if (!cloudXLoadDone || !levelPlayLoadDone) return
if (cloudXAd == null && levelPlayAdInfo == null) {
cloudXLoadDone = false
levelPlayLoadDone = false
loadMissingAds()
return
}
val bids = listOfNotNull(
cloudXAd?.let { CloudXArbiterBid.cloudX(it) },
levelPlayAdInfo?.let { adInfo ->
CloudXArbiterBid.levelPlay(
networkName = adInfo.adNetwork,
revenue = adInfo.revenue,
precision = adInfo.precision,
)
}
)
CloudX.arbiter(
configuration = CloudXArbiterConfiguration.builder(bids).build(),
listener = object : CloudXArbiterListener {
override fun onCompleted(result: CloudXArbiterResult) {
nextWinner = result.platform
listener?.onWinnerPrepared(result.platform)
}
}
)
}
private fun showCloudX(activity: Activity): Boolean {
if (cloudXInterstitial.isAdReady) {
cloudXInterstitial.show(activity)
return true
}
clearCloudXAndLoadMissingAds()
return false
}
private fun showLevelPlay(activity: Activity): Boolean {
if (levelPlayInterstitial.isAdReady) {
levelPlayInterstitial.showAd(activity)
return true
}
clearLevelPlayAndLoadMissingAds()
return false
}
private fun clearCloudXAndLoadMissingAds() {
cloudXAd = null
cloudXLoadDone = false
nextWinner = null
loadMissingAds()
}
private fun clearLevelPlayAndLoadMissingAds() {
levelPlayAdInfo = null
levelPlayLoadDone = false
nextWinner = null
loadMissingAds()
}
private fun createCloudXListener() = object : CloudXInterstitialListener {
override fun onAdLoaded(cloudXAd: CloudXAd) {
this@ArbiterInterstitialController.cloudXAd = cloudXAd
cloudXLoadDone = true
maybePrepareWinner()
}
override fun onAdLoadFailed(adUnitId: String, cloudXError: CloudXError) {
cloudXAd = null
cloudXLoadDone = true
maybePrepareWinner()
}
override fun onAdDisplayed(cloudXAd: CloudXAd) = Unit
override fun onAdDisplayFailed(cloudXAd: CloudXAd, cloudXError: CloudXError) {
clearCloudXAndLoadMissingAds()
}
override fun onAdHidden(cloudXAd: CloudXAd) {
clearCloudXAndLoadMissingAds()
}
override fun onAdClicked(cloudXAd: CloudXAd) = Unit
}
private fun createLevelPlayListener() = object : LevelPlayInterstitialAdListener {
override fun onAdLoaded(levelPlayAdInfo: LevelPlayAdInfo) {
this@ArbiterInterstitialController.levelPlayAdInfo = levelPlayAdInfo
levelPlayLoadDone = true
maybePrepareWinner()
}
override fun onAdLoadFailed(levelPlayAdError: LevelPlayAdError) {
levelPlayAdInfo = null
levelPlayLoadDone = true
maybePrepareWinner()
}
override fun onAdInfoChanged(levelPlayAdInfo: LevelPlayAdInfo) {
this@ArbiterInterstitialController.levelPlayAdInfo = levelPlayAdInfo
}
override fun onAdDisplayed(levelPlayAdInfo: LevelPlayAdInfo) = Unit
override fun onAdDisplayFailed(
levelPlayAdError: LevelPlayAdError,
levelPlayAdInfo: LevelPlayAdInfo
) {
clearLevelPlayAndLoadMissingAds()
}
override fun onAdClosed(levelPlayAdInfo: LevelPlayAdInfo) {
clearLevelPlayAndLoadMissingAds()
}
override fun onAdClicked(levelPlayAdInfo: LevelPlayAdInfo) = Unit
}
}showAtPlacement() returns true only when an ad show call was made. onAdInfoChanged() keeps the cached LevelPlay candidate up to date while it remains loaded.
For PubMatic OpenWrap, create a third-party bid with CloudXArbiterBid.pubmatic(price, partnerName). If the arbiter service is unavailable, the SDK falls back to the highest comparable USD bid among the supplied supported bid inputs.
Banner and MREC arbitration
Banner and MREC are view formats: multiple networks can hold a loaded ad at the same time, but only one view can be on screen. Arbitrating between them adds three requirements that fullscreen formats don’t have.
Disable auto-refresh
Auto-refresh must be turned off everywhere so the arbiter — not each network’s internal timer — controls when a new ad is shown:
- Turn off auto-refresh for the ad unit in the CloudX dashboard.
- Call
stopAutoRefresh()on theCloudXAdViewimmediately after creating it. - Disable auto-refresh on the other arbitrated networks’ banner APIs as well.
View attachment
Load every candidate off-screen. Only the winning bid’s banner view may be attached to the view hierarchy or displayed. Keep losing views unattached — do not add them to a parent view, even temporarily — since attaching a view renders it and can fire an impression for a bid the arbiter rejected. Unlike the standard banner integration, which adds the view to the layout at creation time (see Banner & MREC), with Trusted Arbiter the view must not be attached at creation — attach it only after arbitration selects it as the winner.
Refresh cycle
With auto-refresh disabled, the arbiter must drive its own refresh loop:
Run the first round
Load candidates from every network in parallel, run the arbiter, and attach the winning view.
Start the next load on the winner
As soon as the winner’s impression fires, immediately start loading a new fill from the winning network so it’s ready for the next round.
Retain non-winning fills
Keep the non-winning networks’ already-filled ads for the next arbitration round. Re-request loads only from networks that did not fill in the previous round.
Re-arbitrate
Once the outstanding load responses return, run the arbiter again over the full candidate set.
Swap on an interval
Refresh the displayed ad on a 20–30 second interval by attaching the new winner’s view in place of the old one. Intervals shorter than 20 seconds decrease CPM performance.
Banner example
This example arbitrates a CloudX banner against a LevelPlay banner and drives the refresh cycle above. The same structure applies to MREC — swap CloudX.createBanner() for CloudX.createMREC() and the LevelPlay banner type for the LevelPlay MREC type.
/**
* Drives Trusted Arbiter for a banner placement: loads [cloudXBanner] and
* [levelPlayBanner] in parallel, arbitrates between whichever candidates filled,
* attaches the winning view to [container], and refreshes on [refreshIntervalMs].
*
* Auto-refresh must be disabled on both networks (dashboard setting for CloudX,
* `stopAutoRefresh()` here, and the equivalent LevelPlay setting) — this controller
* owns the refresh cycle instead.
*/
class ArbiterBannerController(
private val cloudXBanner: CloudXAdView,
private val levelPlayBanner: LevelPlayBannerAdView,
private val container: ViewGroup,
private val refreshIntervalMs: Long = 25_000L,
) {
private val handler = Handler(Looper.getMainLooper())
private var cloudXAd: CloudXAd? = null
private var cloudXFilled = false
private var cloudXLoadDone = false
private var levelPlayAdInfo: LevelPlayAdInfo? = null
private var levelPlayFilled = false
private var levelPlayLoadDone = false
private var currentWinner: CloudXArbiterPlatform? = null
private val refreshRunnable = Runnable { runArbitrationRound() }
init {
cloudXBanner.stopAutoRefresh()
cloudXBanner.listener = createCloudXListener()
cloudXBanner.revenueListener = createCloudXRevenueListener()
levelPlayBanner.bannerListener = createLevelPlayListener()
}
/** Starts the first round: loads every network that isn't already holding a fill. */
fun start() {
loadMissingAds()
}
fun stop() {
handler.removeCallbacks(refreshRunnable)
}
private fun loadMissingAds() {
if (!cloudXFilled) cloudXBanner.load()
if (!levelPlayFilled) levelPlayBanner.loadAd()
}
/** Runs once both networks have settled (filled or failed); re-arbitrates only pending candidates. */
private fun maybeArbitrate() {
if (!cloudXLoadDone || !levelPlayLoadDone) return
val bids = listOfNotNull(
cloudXAd?.let { CloudXArbiterBid.cloudX(it) },
levelPlayAdInfo?.let { info ->
CloudXArbiterBid.levelPlay(
networkName = info.adNetwork,
revenue = info.revenue,
precision = info.precision,
)
},
)
if (bids.isEmpty()) {
loadMissingAds()
return
}
CloudX.arbiter(
configuration = CloudXArbiterConfiguration.builder(bids).build(),
listener = object : CloudXArbiterListener {
override fun onCompleted(result: CloudXArbiterResult) {
showWinner(result.platform)
}
}
)
}
private fun runArbitrationRound() {
maybeArbitrate()
}
/** Attaches only the winning view; the losing view is never added to [container]. */
private fun showWinner(platform: CloudXArbiterPlatform) {
currentWinner = platform
container.removeAllViews()
when (platform) {
CloudXArbiterPlatform.CLOUDX -> container.addView(cloudXBanner)
CloudXArbiterPlatform.LEVELPLAY -> container.addView(levelPlayBanner)
else -> return // CloudXArbiterPlatform.NONE — no winner; leave container empty
}
handler.removeCallbacks(refreshRunnable)
handler.postDelayed(refreshRunnable, refreshIntervalMs)
}
/** Starts the winning network's next load immediately after its impression fires. */
private fun onWinnerImpression(platform: CloudXArbiterPlatform) {
when (platform) {
CloudXArbiterPlatform.CLOUDX -> {
cloudXFilled = false
cloudXLoadDone = false
cloudXBanner.load()
}
CloudXArbiterPlatform.LEVELPLAY -> {
levelPlayFilled = false
levelPlayLoadDone = false
levelPlayBanner.loadAd()
}
else -> Unit
}
}
private fun createCloudXListener() = object : CloudXAdViewListener {
override fun onAdLoaded(cloudXAd: CloudXAd) {
this@ArbiterBannerController.cloudXAd = cloudXAd
cloudXFilled = true
cloudXLoadDone = true
maybeArbitrate()
}
override fun onAdLoadFailed(adUnitId: String, cloudXError: CloudXError) {
cloudXAd = null
cloudXFilled = false
cloudXLoadDone = true
maybeArbitrate()
}
override fun onAdClicked(cloudXAd: CloudXAd) = Unit
override fun onAdExpanded(cloudXAd: CloudXAd) = Unit
override fun onAdCollapsed(cloudXAd: CloudXAd) = Unit
}
private fun createCloudXRevenueListener() = object : CloudXAdRevenueListener {
override fun onAdRevenuePaid(cloudXAd: CloudXAd) {
// onAdRevenuePaid fires at CloudX impression time; only act on it while CloudX is showing.
if (currentWinner == CloudXArbiterPlatform.CLOUDX) {
onWinnerImpression(CloudXArbiterPlatform.CLOUDX)
}
}
}
private fun createLevelPlayListener() = object : LevelPlayBannerAdViewListener {
override fun onAdLoaded(levelPlayAdInfo: LevelPlayAdInfo) {
this@ArbiterBannerController.levelPlayAdInfo = levelPlayAdInfo
levelPlayFilled = true
levelPlayLoadDone = true
maybeArbitrate()
}
override fun onAdLoadFailed(levelPlayAdError: LevelPlayAdError) {
levelPlayAdInfo = null
levelPlayFilled = false
levelPlayLoadDone = true
maybeArbitrate()
}
override fun onAdDisplayed(levelPlayAdInfo: LevelPlayAdInfo) {
if (currentWinner == CloudXArbiterPlatform.LEVELPLAY) {
onWinnerImpression(CloudXArbiterPlatform.LEVELPLAY)
}
}
override fun onAdClicked(levelPlayAdInfo: LevelPlayAdInfo) = Unit
}
}onAdRevenuePaid() (CloudX) and onAdDisplayed() (LevelPlay) are where each network’s impression fires; that is where the winning network’s next load is kicked off, keeping a fresh fill ready for the following round without holding up the current display. Networks that already have a filled, unattached ad skip straight past loadMissingAds() until their fill is used or expires.
Custom Bid Inputs
Use CloudXArbiterBid.custom() when you want Trusted Arbiter to compare CloudX with a third-party platform that does not have a dedicated bid helper.
val customBid = CloudXArbiterBid.custom(
platformName = "my_mediation_platform",
networkName = "winning_demand_source",
revenuePerImpressionUSD = 0.00125,
precision = CloudXArbiterPrecision.EXACT,
extras = mapOf("ad_unit" to "third-party-ad-unit-id"),
)
val configuration = CloudXArbiterConfiguration.builder(
listOf(CloudXArbiterBid.cloudX(cloudXAd), customBid)
).build()When a custom bid wins, result.platform is CloudXArbiterPlatform.CUSTOM and result.platformName contains the platformName supplied on the bid. Pass revenuePerImpressionUSD as revenue for one impression in USD, not CPM. Use CloudXArbiterPrecision.EXACT, ESTIMATED, PUBLISHER_DEFINED, or UNDEFINED to describe that revenue value.