BiddingStack Android SDK

BiddingStack Android SDK Integration Guide

BiddingStack is a lightweight header bidding SDK for Android built on OpenRTB 2.5. It enables publishers to fetch real-time bids and integrate them with Google Ad Manager (GAM) or render them directly. View Changelog.

Requirements

ComponentMinimum Version
Android Min SDK23 (Android 6.0)
Compile SDK35
Java17
Kotlin2.1.0+

Installation

Add the biddingstack-core-release.aar and biddingstack-gam-adapter-release.aar to your project.

Dependencies (build.gradle)

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.aar'])
    implementation 'com.google.android.gms:play-services-ads:25.3.0'
    implementation 'com.google.ads.interactivemedia.v3:interactivemedia:3.39.0'
    implementation "androidx.media3:media3-exoplayer:1.9.4"
    implementation "androidx.media3:media3-ui:1.9.4"
    implementation "androidx.media3:media3-exoplayer-ima:1.9.4"
    implementation 'androidx.core:core-ktx:1.12.0'
}

Permissions (AndroidManifest.xml)

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

<application ...>
    <meta-data
        android:name="com.google.android.gms.ads.APPLICATION_ID"
        android:value="ca-app-pub-XXXXXXXXXXXXXXXX~XXXXXXXXXX"/>
</application>

SDK Initialization

import com.biddingstack.core.BiddingStack

class MyApp : Application() {
    override fun onCreate() {
        super.onCreate()
        BiddingStack.shared.initialize(
            context = this,
            accountId = "YOUR_ACCOUNT_ID",
            enableTestMode = true
        ) {
            // SDK initialized, config loaded
        }
    }
}

Initialize Parameters

ParameterTypeDefaultDescription
contextContextrequiredApplication context
accountIdStringrequiredYour BiddingStack Account ID
timeoutInt2000Bid request timeout in ms
enableTestModeBooleanfalseEnable debug logging and test mode
enableCMPBooleantrueAuto-read IAB CMP strings from SharedPreferences
completion(() -> Unit)?nullCalled after remote config is loaded

Note: App bundle, name, store URL, and GAM ad unit paths are resolved automatically from the remote config.

Optional Configuration

// User data
BiddingStack.shared.userId = "user_123"
BiddingStack.shared.userGender = "M"
BiddingStack.shared.userYob = 1990

// Privacy
BiddingStack.shared.coppa = false
BiddingStack.shared.gdprConsentString = "TCF_consent_string"
BiddingStack.shared.gdprApplies = true
BiddingStack.shared.usPrivacyString = "1YNN"
BiddingStack.shared.gppString = "gpp_string"
BiddingStack.shared.gppSid = listOf(2)

// App metadata
BiddingStack.shared.appStoreUrl = "https://play.google.com/store/apps/details?id=com.example"
BiddingStack.shared.appCategories = listOf("IAB1", "IAB2")

// Supply chain
BiddingStack.shared.schainObject = "{...}"
BiddingStack.shared.externalUserIds = mapOf("partner" to "uid_abc")
BiddingStack.shared.buyerUid = "buyer_123"
BiddingStack.shared.userLatLng = Pair(37.7749, -122.4194)

Content URL

Pass the web URL of the content the user is currently viewing so it is included in every bid request as app.content.url. This is important for brand safety and yield: it gives bidders page-level context to verify where their ads run, which increases bid rates and CPMs. Set it before loading ads and update it on each screen change; set null to clear.

BiddingStack.shared.contentUrl = "https://www.example.com/article/12345"

Available since SDK 26.7.1.

Ad Formats

val bannerAdUnit = BiddingStackBannerAdUnit(
    configId = "YOUR_CONFIG_ID",
    gamAdUnitId = "YOUR_GAM_AD_UNIT_ID", // optional fallback if config API is unavailable
    adSizes = listOf(BiddingStackAdSize(300, 250))
)

BiddingStack.shared.fetchDemand(bannerAdUnit) { result ->
    runOnUiThread {
        val builder = AdManagerAdRequest.Builder()
        result.onSuccess { keywords ->
            BiddingStackGAMUtils.apply(keywords, builder)
        }
        val gamView = AdManagerAdView(context)
        gamView.adUnitId = bannerAdUnit.resolvedGamAdUnitId
        gamView.setAdSizes(AdSize.MEDIUM_RECTANGLE)
        gamView.loadAd(builder.build())
    }
}

Interstitial Ad

val interstitialAdUnit = BiddingStackInterstitialAdUnit(
    configId = "YOUR_CONFIG_ID"
)

BiddingStack.shared.fetchDemand(interstitialAdUnit) { result ->
    runOnUiThread {
        val builder = AdManagerAdRequest.Builder()
        result.onSuccess { keywords ->
            BiddingStackGAMUtils.apply(keywords, builder)
        }
        AdManagerInterstitialAd.load(
            context, interstitialAdUnit.resolvedGamAdUnitId,
            builder.build(),
            object : AdManagerInterstitialAdLoadCallback() {
                override fun onAdLoaded(ad: AdManagerInterstitialAd) {
                    ad.show(activity)
                }
            }
        )
    }
}

App Open Ad

App open ads show at natural app-entry points, such as launch or returning from the background. Preload the ad with BiddingStackInterstitialBanner, then show it when the app becomes active.

object AppOpenAdManager : BiddingStackInterstitialDelegate {

    private var interstitial: BiddingStackInterstitialBanner? = null
    private var isLoading = false
    private var appContext: Context? = null

    fun loadAd(context: Context) {
        appContext = context.applicationContext
        if (isLoading || interstitial?.isAdReady() == true) return
        isLoading = true

        // Google interstitial size: 320x480 phone, 768x1024 tablet
        val isTablet = context.resources.configuration.smallestScreenWidthDp >= 600
        val interstitialSize = if (isTablet) BiddingStackAdSize(768, 1024) else BiddingStackAdSize(320, 480)
        val adUnit = BiddingStackBannerAdUnit(
            configId = "YOUR_CONFIG_ID",
            gamAdUnitId = "YOUR_GAM_AD_UNIT_ID",
            adSizes = listOf(interstitialSize, BiddingStackAdSize(300, 250)),
            autoRefreshEnabled = false
        )

        val banner = BiddingStackInterstitialBanner(context, adUnit)
        banner.delegate = this
        banner.setAdServerProvider(GAMBannerProvider(context, adUnit.resolvedGamAdUnitId, adUnit.adSizes))
        interstitial = banner
        banner.loadAd()
    }

    fun showAdIfAvailable(activity: Activity) {
        val banner = interstitial
        if (banner == null || !banner.isAdReady()) {
            loadAd(activity)
            return
        }
        banner.show(activity)
    }

    override fun interstitialDidReceiveAd() { isLoading = false }
    override fun interstitialDidFailToReceiveAd(error: Exception) {
        isLoading = false
        interstitial = null
    }
    override fun interstitialWillPresent() {}
    override fun interstitialDidDismiss() {
        interstitial = null
        appContext?.let { loadAd(it) } // Preload the next one
    }
}

Show it when the app comes to the foreground, e.g. in your main activity:

override fun onStart() {
    super.onStart()
    AppOpenAdManager.showAdIfAvailable(this)
}

Rewarded Ad

val rewardedAdUnit = BiddingStackRewardedAdUnit(
    configId = "YOUR_CONFIG_ID"
)

BiddingStack.shared.fetchDemand(rewardedAdUnit) { result ->
    val builder = AdManagerAdRequest.Builder()
    result.onSuccess { keywords -> BiddingStackGAMUtils.apply(keywords, builder) }
    RewardedAd.load(context, rewardedAdUnit.resolvedGamAdUnitId,
        builder.build(), object : RewardedAdLoadCallback() {
        override fun onAdLoaded(ad: RewardedAd) {
            ad.show(activity) { reward -> /* Grant reward */ }
        }
    })
}

Native Ad

val nativeAdUnit = BiddingStackNativeAdUnit(
    configId = "YOUR_CONFIG_ID"
)

BiddingStack.shared.loadNativeAd(nativeAdUnit) { ad, error ->
    if (ad != null) {
        myTitleTextView.text = ad.title
        myBodyTextView.text = ad.body
        ad.registerView(myContainer, listOf(myCtaButton))
    }
}

BiddingStackNativeAd Properties

PropertyTypeDescription
titleString?Ad headline
bodyString?Ad body text
callToActionString?CTA button text
sponsoredByString?Sponsor/advertiser name
iconUrlString?Icon image URL
mainImageUrlString?Main image URL
clickUrlString?Click-through URL

Outstream Video

val videoAdUnit = BiddingStackBannerAdUnit(
    configId = "VIDEO_CONFIG_ID",
    adSizes = listOf(BiddingStackAdSize(300, 250)),
    formats = listOf(BiddingStackFormat.VIDEO)
)

val videoView = BiddingStackBannerView(context)
videoView.adUnit = videoAdUnit
videoView.videoRenderer = BiddingStackIMAVideoRenderer(context)
videoView.loadAd()

Advanced Features

Bid Preloading

BiddingStack.shared.preloadBid(bannerAdUnit)
// Later, fetchDemand returns the cached bid if valid (30s TTL)

Custom Targeting

BiddingStack.shared.addTargetingKV("interest", "tech")
BiddingStack.shared.addTargetingKV("interest", listOf("tech", "gaming"))
BiddingStack.shared.removeTargetingKey("interest")
BiddingStack.shared.clearTargetingKV()

// Per ad unit
bannerAdUnit.addContextKeyword("section", "sports")

PMP Deals

val deal = BiddingStackDeal(id = "deal_123", bidfloor = 5.0, bidfloorcur = "USD")
bannerAdUnit.deals = listOf(deal)

Privacy & Compliance

The SDK automatically reads IAB consent strings from SharedPreferences:

  • GDPR: IABTCF_TCString
  • CCPA: IABUSPrivacy_String
  • GPP: IABGPP_HDR_GppString

Debug Logging

BiddingStack.shared.initialize(..., enableTestMode = true)
// Or: BSLogger.isEnabled = true

Enable on any build via adb

Debug logs can be enabled at runtime on any device — including production/release builds — without code changes:

# Lifecycle, auction results, and GAM targeting
adb shell setprop log.tag.BiddingStack DEBUG

# Additionally dumps full bid request/response JSON
adb shell setprop log.tag.BiddingStack VERBOSE

# View logs
adb logcat -s BiddingStack

The property takes effect immediately and clears on device reboot.