Skip to main content
Version: 4.2.0

Integration Guide

Complete guide to integrating the BidMachine Plus Android SDK.

BidMachine Plus Android SDK

BidMachine Plus exposes two demand paths through one API:

ModeDescription
AdNetworkBidMachine runs as a header-bidding demand source — on its own, or plugged into a third-party mediation
MediationBidMachine acts as the mediation platform itself, running the auction across your demand

The same ad unit classes — BannerAd, InterstitialAd, RewardedAd — work in both modes. The active mode is selected at initialization.

This page covers SDK installation, initialization, and ad loading. Two installation paths are available: automated via AI coding agents, or manual Gradle setup.

Automated Integration with AI Coding Agents

The BidMachine Plus SDK Agents bundle ships a portable integration skill for Claude Code, Codex, and Gemini. Install it for your runtime:

/plugin marketplace add bidmachine/bidmachine-sdk-agents
/plugin install bidmachine-sdk-agents@bidmachine

Then ask your agent to integrate BidMachine Plus — for example:

Prompt
Integrate BidMachine Plus into my Android app with interstitial, rewarded, and banner ads.

The bundled skill drives the integration: it adds the SDK dependency, initializes in AdNetwork or Mediation mode, wires ad units, and sets privacy flags (GDPR, CCPA), preserving any existing ad setup.

Manual Installation

Requires Android SDK 23+, Gradle 8.7+, Android Gradle Plugin 8.6+, Kotlin 2.1+, and Java 17 source/target compatibility.

Add the BidMachine Maven repository and the SDK dependency to your app-level build.gradle:

app/build.gradle
repositories {
maven { url "https://artifactory.bidmachine.io/bidmachine" }
}

dependencies {
implementation "io.bidmachine.plus:sdk:0.1.0" // BidMachine Plus SDK
implementation "com.google.android.gms:play-services-ads-identifier:18.2.0" // required for advertising ID
}

Network Security Configuration

The SDK needs network access — add the INTERNET permission and reference a network security config in AndroidManifest.xml:

AndroidManifest.xml
<manifest>
<uses-permission android:name="android.permission.INTERNET" />
<application android:networkSecurityConfig="@xml/network_security_config" />
</manifest>

Create res/xml/network_security_config.xml:

res/xml/network_security_config.xml
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system" />
</trust-anchors>
</base-config>
<debug-overrides>
<trust-anchors>
<certificates src="user" />
</trust-anchors>
</debug-overrides>
</network-security-config>

Initialization

Call BidMachine.initialize once at app startup. Pass the desired mode to select the demand path.

BidMachine.instance returns the SDK instance — keep a reference; you'll pass it to every ad unit constructor.

BidMachine Plus initializes in one of two modes, selected by the IntegrationType you pass to the builder:

AdNetwork Mode

BidMachine runs as a header-bidding ad network. When it plugs into a third-party mediation, name that platform with withMediator so it can compete in the waterfall.

val sdk = BidMachine.instance(context, "YOUR_APP_KEY")

val config = InitializationConfigBuilder(IntegrationType.AdNetwork)
.withMediator(MediatorName.ADMOB) // the mediation BidMachine plugs into — or LEVEL_PLAY, MAX, a custom String
.withLoggingEnabled(true) // remove before release
.withTestModeEnabled(true) // remove before release
.build()

sdk.initialize(config) { status, error ->
if (error != null) Log.e("BidMachine", "init failed: ${error.message}")
}

Mediation Mode

BidMachine acts as the mediation platform itself, running the auction across your demand. Pass IntegrationType.Mediation — there's no third-party mediator to declare:

val config = InitializationConfigBuilder(IntegrationType.Mediation)
.withLoggingEnabled(true) // remove before release
.withTestModeEnabled(true) // remove before release
.build()

BidMachine.instance(context, "YOUR_APP_KEY").initialize(config) { status, error -> }

In both modes the ad unit classes expose notifyWin() and notifyLoss(winnerEcpm, networkName) for reporting the outcome of a waterfall round — see Mediation.

Running the auction on your own server instead? See S2S Bidding for the bid-token flow and loading with a bidPayload.

Ad Integration

All ad lifecycle callbacks deliver an AdInfo describing the winning ad:

PropertyTypeDescription
placementIdStringPlacement ID from the BidMachine dashboard
priceDoubleeCPM ÷ 1000 (e.g. 0.005 = $5 CPM)
precisionRevenuePrecisionOne of Estimated, Exact, Unknown
infoMap<String, String>Network metadata. Known keys: networkName, dsp, ecpm

Read the winning network via adInfo.info["networkName"], and the per-impression revenue via adInfo.price.

adInfo.precision describes how reliable that price is:

ValueMeaning
ExactReal-time auction price — trust for reporting
EstimatedHistorical-data estimate — treat as approximate
UnknownConfidence cannot be determined

All callbacks fire on the main thread, so you can update UI from them directly.

Interstitial Ads

Full-screen ads. Create once, load, then call show when ready. Reload from onAdClosed to keep an ad ready for the next show.

MainActivity.kt
class MainActivity : AppCompatActivity(), InterstitialListener {
private lateinit var interstitialAd: InterstitialAd

private fun createInterstitialAd() {
interstitialAd = InterstitialAd(sdk, placementId = "YOUR_PLACEMENT_ID")
interstitialAd.listener = this
interstitialAd.load(this)
}

private fun showInterstitialAd() {
if (interstitialAd.isLoaded) {
interstitialAd.show(this)
} else {
Log.w("BidMachine", "Interstitial ad not ready yet")
}
}

override fun onDestroy() {
super.onDestroy()
interstitialAd.destroy()
}

// InterstitialListener callbacks
override fun onAdLoaded(adInfo: AdInfo) {
Log.d("BidMachine", "Interstitial loaded from ${adInfo.info["networkName"]}")
}

override fun onAdLoadFailed(adInfo: AdInfo?, error: BidMachineError) {
Log.e("BidMachine", "Interstitial load failed: ${error.message}")
}

override fun onAdShown(adInfo: AdInfo) {
Log.d("BidMachine", "Interstitial displayed")
}

override fun onAdShowFailed(adInfo: AdInfo?, error: BidMachineError) {
Log.e("BidMachine", "Interstitial show failed: ${error.message}")
}

override fun onAdClicked(adInfo: AdInfo) {
Log.d("BidMachine", "Interstitial clicked")
}

override fun onAdClosed(adInfo: AdInfo) {
Log.d("BidMachine", "Interstitial closed")
interstitialAd.load(this) // reload for next show
}

override fun onAdExpired(adInfo: AdInfo) {
Log.w("BidMachine", "Interstitial expired before show")
}

override fun onAdRevenuePaid(adInfo: AdInfo) {
Log.d("BidMachine", "Interstitial revenue: ${adInfo.price} from ${adInfo.info["networkName"]}")
}
}
CallbackDescription
onAdLoadedAd loaded and ready to show
onAdLoadFailedAd failed to load
onAdShownAd is displayed full-screen
onAdShowFailedAd failed to display
onAdClickedUser tapped the ad
onAdClosedUser dismissed the ad
onAdExpiredAd expired before being shown
onAdRevenuePaidBillable impression recorded

Rewarded Ads

Same lifecycle as interstitial, plus an onAdRewarded callback when the user completes the ad and earns a reward.

MainActivity.kt
class MainActivity : AppCompatActivity(), RewardedListener {
private lateinit var rewardedAd: RewardedAd

private fun createRewardedAd() {
rewardedAd = RewardedAd(sdk, placementId = "YOUR_PLACEMENT_ID")
rewardedAd.listener = this
rewardedAd.load(this)
}

private fun showRewardedAd() {
if (rewardedAd.isLoaded) {
rewardedAd.show(this)
} else {
Log.w("BidMachine", "Rewarded ad not ready yet")
}
}

override fun onDestroy() {
super.onDestroy()
rewardedAd.destroy()
}

// RewardedListener callbacks
override fun onAdLoaded(adInfo: AdInfo) {
Log.d("BidMachine", "Rewarded loaded from ${adInfo.info["networkName"]}")
}

override fun onAdRewarded(adInfo: AdInfo, reward: Reward?) {
Log.d("BidMachine", "Reward earned")
// grant reward to the user
}

override fun onAdLoadFailed(adInfo: AdInfo?, error: BidMachineError) {
Log.e("BidMachine", "Rewarded load failed: ${error.message}")
}

override fun onAdShown(adInfo: AdInfo) {}
override fun onAdShowFailed(adInfo: AdInfo?, error: BidMachineError) {}
override fun onAdClicked(adInfo: AdInfo) {}

override fun onAdClosed(adInfo: AdInfo) {
rewardedAd.load(this) // reload for next show
}

override fun onAdExpired(adInfo: AdInfo) {}

override fun onAdRevenuePaid(adInfo: AdInfo) {
Log.d("BidMachine", "Rewarded revenue: ${adInfo.price} from ${adInfo.info["networkName"]}")
}
}
CallbackDescription
onAdLoadedAd loaded and ready to show
onAdRewardedUser completed the ad, grant reward
onAdLoadFailedAd failed to load
onAdShownAd is displayed full-screen
onAdShowFailedAd failed to display
onAdClickedUser tapped the ad
onAdClosedUser dismissed the ad
onAdExpiredAd expired before being shown
onAdRevenuePaidBillable impression recorded

The Reward object passed to onAdRewarded exposes label: String and amount: Int.

BannerAd extends ViewGroup. Attach it to your own layout, or call show(position) to display it at a fixed screen position.

SizeDimensionsUse case
Banner320 × 50Standard banner
Leaderboard728 × 90Tablets
MREC300 × 250Medium rectangle
BannerAdSize.adaptive(width, maxHeight)customFluid layout
tip

Use BannerAdSize.getMaxAdaptiveHeight(width) to calculate maxHeight for adaptive banners.

Manual Banner

Place a container in your layout that will host the banner view:

res/layout/activity_main.xml
<FrameLayout
android:id="@+id/ad_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom" />

Resolve it once in onCreate, then attach the loaded BannerAd to it inside onAdLoaded:

MainActivity.kt
class MainActivity : AppCompatActivity(), BannerListener {
private lateinit var bannerAd: BannerAd
private lateinit var adContainer: FrameLayout

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
adContainer = findViewById(R.id.ad_container)
}

private fun createBannerAd() {
bannerAd = BannerAd(this, sdk, placementId = "YOUR_PLACEMENT_ID", size = BannerAdSize.Banner)
bannerAd.listener = this
bannerAd.load(this)
}

override fun onDestroy() {
super.onDestroy()
bannerAd.destroy()
}

// BannerListener callbacks
override fun onAdLoaded(adInfo: AdInfo) {
Log.d("BidMachine", "Banner loaded from ${adInfo.info["networkName"]}")
adContainer.addView(bannerAd)
}

override fun onAdLoadFailed(adInfo: AdInfo?, error: BidMachineError) {
Log.e("BidMachine", "Banner load failed: ${error.message}")
}

override fun onAdShown(adInfo: AdInfo) {}
override fun onAdShowFailed(adInfo: AdInfo?, error: BidMachineError) {}
override fun onAdClicked(adInfo: AdInfo) {}
override fun onAdExpired(adInfo: AdInfo) {}

override fun onAdRevenuePaid(adInfo: AdInfo) {
Log.d("BidMachine", "Banner revenue: ${adInfo.price} from ${adInfo.info["networkName"]}")
}
}
CallbackDescription
onAdLoadedAd loaded and ready to attach to a container
onAdLoadFailedAd failed to load
onAdShownAd visible on screen, impression tracked
onAdShowFailedAd failed to display
onAdClickedUser tapped the ad
onAdExpiredAd expired before being shown
onAdRevenuePaidBillable impression recorded

Positioned Banner

Call show(position) on a loaded BannerAd to display it at one of the positions below. Call hide() to remove it from the screen.

PositionConstant
TopBannerPosition.HorizontalTop
BottomBannerPosition.HorizontalBottom
LeftBannerPosition.VerticalLeft
RightBannerPosition.VerticalRight
val bannerAd = BannerAd(this, sdk, placementId = "YOUR_PLACEMENT_ID", size = BannerAdSize.Banner)
bannerAd.listener = object : BannerListener {
override fun onAdLoaded(adInfo: AdInfo) {
bannerAd.show(BannerPosition.HorizontalBottom) // default position
}
override fun onAdLoadFailed(adInfo: AdInfo?, error: BidMachineError) {}
override fun onAdShown(adInfo: AdInfo) {}
override fun onAdShowFailed(adInfo: AdInfo?, error: BidMachineError) {}
override fun onAdClicked(adInfo: AdInfo) {}
override fun onAdExpired(adInfo: AdInfo) {}
override fun onAdRevenuePaid(adInfo: AdInfo) {}
}
bannerAd.load(this)

// Later:
bannerAd.hide() // remove the overlay; show(position) can re-display it
bannerAd.destroy()

Publisher Extras

Attach arbitrary key → value pairs forwarded with the bid request as publisher extras. The API exists at two levels with the same shape — choose the one that matches the scope you need.

ScopeWhere to callApplies to
SDK-widesdk.addExtraEvery auction from this instance
Per ad unit<AdUnit>.addExtraOnly that placement's auctions

Passing null as the value removes the key. Per-ad-unit extras override SDK-wide values for the same key on that placement.

SDK-wide

val sdk = BidMachine.instance(context, "YOUR_APP_KEY")

sdk.addExtra("user_segment", "whale")
sdk.addExtra("ab_bucket", "control")

val all: Map<String, String> = sdk.extras
sdk.addExtra("ab_bucket", null) // remove

Per ad unit

Available on BannerAd, InterstitialAd, and RewardedAd:

interstitialAd.addExtra("placement_context", "level_complete")
bannerAd.addExtra("screen", "main_menu")

val extras: Map<String, String> = interstitialAd.extras