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.
Call CloudX.arbiter() after CloudX.initialize() has completed. Until the arbiter service is available, the SDK falls back to selecting the highest comparable USD bid from the supplied inputs.
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).
Kotlin callers can use the suspending overload instead of a listener. It returns the same CloudXArbiterResult:val result = CloudX.arbiter(configuration)
Log.d("CloudX", "Selected platform: ${result.platform.name}")
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.
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.
ArbiterInterstitialController.kt
/**
* 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.