Floxis Maven/Documentation

Floxis SDK guide for Google Ad Manager (Android)#

The Floxis SDK for Google Ad Manager adds Floxis demand to an Android app that already serves ads through Google Ad Manager (GAM). Before each GAM request, Floxis runs one auction. Its price tier goes to GAM as a key-value. When a Floxis line item wins in GAM, the SDK shows the Floxis ad in your app.

Availability: floxis-sdk-gam is available from the next SDK release. It is not in the Maven repository yet, so the dependency line below does not resolve today. Contact your Floxis account manager for early access.

If your app uses AdMob mediation rather than Ad Manager line items, use the In-app SDK (Android) guide. For AppLovin MAX or Unity LevelPlay, use the guide for that platform. Related guides: SDK privacy and consent, SDK testing and troubleshooting, SDK API reference and SDK release notes.

Before you start#

On the Supply page, set up an endpoint for the app with the Integration type Floxis SDK. You need two things from it:

  • The seat and region. Open the saved endpoint; its integration panel shows both.
  • The price tiers, from the endpoint's AdMob price tiers field. These are the prices Floxis can win at, and you create one GAM line item per tier, so the field applies to GAM too despite its name. Left empty, it means every $0.10 step from 0.10 to 20.00 (200 tiers).

You also need an Ad Manager network with mobile app ad units for each format, and permission to create orders, line items, key-values and creatives in it.

Requirements#

ItemValue
Android minSdk24
App compileSdk35
Java / Kotlin target17
Google Mobile Ads25.5.0 or newer (play-services-ads, added for you)
Prebid Mobile3.4.0 with its GAM event handlers (added for you)
Cannot share an app withfloxis-sdk-nextgen (Google's Next-Gen Mobile Ads SDK)

How it works#

  1. Floxis auction. When you call load(), the SDK runs one Floxis auction for the placement (1.5 s deadline by default).
  2. GAM request. If Floxis bids, the bid's key-values are added to the GAM ad request: hb_pb (the price tier, for example 1.20), hb_bidder = floxis, hb_env = mobile-app, and hb_size (for example 320x50) when the bid has a size.
  3. GAM decides. GAM weighs your Floxis line item for that tier against everything else that can serve.
  4. Floxis renders. If the Floxis line item wins, its creative sends an app event named PrebidAppEvent. The SDK receives it and shows the Floxis ad. If anything else wins, GAM shows its own ad as usual.

If Floxis does not bid or does not answer in time, GAM is requested without Floxis key-values. If Floxis bid but GAM returns no ad at all, the Floxis ad is shown.

The Floxis ad is rendered in the app from the bid itself. Nothing is fetched from a cache, so there is nothing extra to set up for that.

1. Add the SDK#

Add the Floxis repository to settings.gradle.kts. The exclusiveContent filter makes Gradle look there only for tech.floxis packages:

dependencyResolutionManagement {
  repositories {
    google(); mavenCentral()
    exclusiveContent {
      forRepository { maven("https://maven.floxis.tech/releases") }
      filter { includeGroup("tech.floxis") }
    }
  }
}

Then add the dependency to app/build.gradle.kts, with the version your account manager gives you:

implementation("tech.floxis:floxis-sdk-gam:<version>")

Gradle also brings in Prebid Mobile 3.4.0, Prebid's GAM event handlers 3.4.0 and Google Mobile Ads 25.5.0, so you don't add them yourself. Floxis classes are in com.floxis.sdk and com.floxis.sdk.gam. The ad listeners are Prebid's, in org.prebid.mobile.api.rendering.listeners.

Other Floxis artifacts. floxis-sdk-gam can share an app with floxis-sdk (AdMob), floxis-sdk-max and floxis-sdk-levelplay. It cannot share one with floxis-sdk-nextgen: Gradle stops that build with a capability conflict that names both. Keep every tech.floxis artifact on the same version.

R8 / ProGuard. The SDK ships its own keep rules. You don't add any.

Manifest. Google Mobile Ads needs your app ID in AndroidManifest.xml, as in Google's Ad Manager quick start:

<meta-data
    android:name="com.google.android.gms.ads.APPLICATION_ID"
    android:value="YOUR_APP_ID" />

The SDK adds the INTERNET and ACCESS_NETWORK_STATE permissions. It never asks for location.

2. Initialise#

Start Google Mobile Ads, then initialise Floxis from its callback. Use the seat and region from your endpoint's integration panel:

MobileAds.initialize(this) {
    runOnUiThread {
        Floxis.initialize(
            applicationContext,
            FloxisConfig(seat = "YOUR_SEAT", region = "us-e", timeoutMs = 1500),
        ) { status, description -> Log.d("Floxis", "init $status $description") }
    }
}
OptionWhat it does
timeoutMsThe longest the SDK waits for Floxis before GAM is requested without it (200–10,000 ms, default 1,500).
debugTurns on Floxis log lines (tag Floxis) and Prebid's debug logging. Warnings are logged either way.
testModeMarks auctions as test traffic. It only works in a debuggable build; a release build ignores it and logs FLX-105.
enabled / Floxis.setEnabled()Kill switch. false stops new Floxis auctions; GAM is still requested, without Floxis.
personalizedAds / Floxis.setPersonalizedAds()false skips the Floxis auction and adds npa=1 to the GAM request.
storeUrlThe store URL sent to buyers. Defaults to the store URL set on Prebid, then your app's Play Store page.
shareLocationDefault true. Sends the last known location only when your app already holds a location permission and consent allows it.

The listener is called once, on the main thread. Ads loaded before initialisation finishes go to GAM without a Floxis auction. A failed initialisation is retried on a later load, at most once every 30 seconds.

FloxisConfig throws IllegalArgumentException for a malformed seat or region, a timeout outside 200–10,000 ms, or a blank storeUrl.

If the app also uses floxis-sdk or floxis-sdk-max, this one Floxis.initialize call covers them too.

3. Set up Google Ad Manager#

Floxis takes part in GAM through price-priority line items, one per price tier. Set this up once per format.

Key-values#

  1. In Ad Manager, open Inventory › Key-values and create the key hb_pb with predefined values. Add every price tier from the endpoint's AdMob price tiers field, with two decimals, exactly as Floxis sends them: 0.10, 0.20, … 20.00.
  2. Optional: create the key hb_bidder with the value floxis. Add it to the line item targeting if other demand in the app also sets hb_pb.

Do not target hb_format. Floxis never sends it, so a line item that targets it never serves. Keep formats apart by targeting the right ad units instead.

Orders and line items#

  1. Create an advertiser for Floxis and an order under it. An order holds up to 450 line items, so the default 200 tiers fit in one order per format. A longer tier list needs a second order.
  2. Create one line item per price tier:
    • Type: Price priority.
    • Rate: the tier, for example 1.20 for the 1.20 line item.
    • Expected creatives: the sizes of the ad units it targets, for example 320x50 for a banner.
    • Inventory: the mobile app ad units for this format.
    • Key-value targeting: hb_pb is the tier, for example hb_pb is 1.20.
  3. Create every tier. Floxis rounds each winning price down to the nearest tier at or below it, and never bids below your lowest tier, so every Floxis win matches a tier in your list. A tier without a line item loses that bid.
  4. When you change the endpoint's price tiers, add or remove line items to match.

Line item names are up to you. A pattern like Floxis banner 1.20 keeps reports readable.

Creatives#

Each line item needs a creative. It tells the app that Floxis won; it does not contain the ad.

Banner and interstitial (display or video). Use a third-party creative at the line item's sizes, with this code snippet:

<script src="https://media.admob.com/api/v1/google_mobile_app_ads.js"></script>
<script>
  admob.events.dispatchAppEvent('PrebidAppEvent', '%%PATTERN:bidid%%');
</script>

Rewarded. Use a video line item with a VAST redirect creative that points at this URL:

https://cdn.jsdelivr.net/npm/prebid-universal-creative/dist/prebid-mobile-rewarded-vast.xml

This is Prebid's published creative. Its ad title, PrebidAppEvent, is how the SDK knows the Floxis line item won.

One creative can be shared by all line items of a format.

Ad units#

  • Banner ad units: turn off GAM's automatic refresh. A GAM refresh does not run a new Floxis auction. Refresh from the app instead (see Banner refresh below).
  • Use the full ad unit path, for example /1234567/app_banner, as gamAdUnitId in your code.

4. Load ads#

configId is your name for the placement, for example home_banner or level_end. Floxis reports by placement, so give each placement its own name and keep it the same across app versions.

One object per request. Each Floxis GAM object loads once. For the next ad, including a banner refresh or a retry after a failure, call destroy() on the old object and create a new one. A second load() on the same object loads nothing and reports an error to your listener.

Create the objects on the main thread. load() and show() can be called from any thread; the work runs on the main thread. After destroy() no more callbacks reach your listener.

The constructors throw IllegalArgumentException for a blank gamAdUnitId or configId, and the banner also for an empty size list.

The examples below run inside an Activity. container is the ViewGroup that holds the banner, and grantReward() is your own code.

import com.floxis.sdk.FloxisSize
import com.floxis.sdk.gam.FloxisGamBannerAd
import org.prebid.mobile.api.exceptions.AdException
import org.prebid.mobile.api.rendering.BannerView
import org.prebid.mobile.api.rendering.listeners.BannerViewListener

private var banner: FloxisGamBannerAd? = null

private fun loadBanner() {
    banner?.destroy()
    banner = FloxisGamBannerAd(
        context = this,
        gamAdUnitId = "/1234567/app_banner",
        configId = "home_banner",
        sizes = listOf(FloxisSize(320, 50)),
        listener = object : BannerViewListener {
            override fun onAdLoaded(bannerView: BannerView) {}
            override fun onAdDisplayed(bannerView: BannerView) {}
            override fun onAdFailed(bannerView: BannerView, exception: AdException) {}
            override fun onAdClicked(bannerView: BannerView) {}
            override fun onAdClosed(bannerView: BannerView) {}
        },
    ).also {
        container.removeAllViews()
        container.addView(it.view)
        it.load()
    }
}

override fun onDestroy() { banner?.destroy(); super.onDestroy() }

view is the banner view for your layout. The first size in sizes is the main size; add more if the GAM ad unit accepts them.

The banner loads once and does not refresh by itself. To refresh, call loadBanner() again from your own timer, for example every 60 seconds while the screen is visible. Each call runs a new Floxis auction and a new GAM request.

Interstitial#

import com.floxis.sdk.FloxisInterstitialFormat
import com.floxis.sdk.FloxisInterstitialOptions
import com.floxis.sdk.gam.FloxisGamInterstitialAd
import org.prebid.mobile.api.exceptions.AdException
import org.prebid.mobile.api.rendering.InterstitialAdUnit
import org.prebid.mobile.api.rendering.listeners.InterstitialAdUnitListener

private var interstitial: FloxisGamInterstitialAd? = null

private fun loadInterstitial() {
    interstitial?.destroy()
    interstitial = FloxisGamInterstitialAd(
        activity = this,
        gamAdUnitId = "/1234567/app_interstitial",
        configId = "level_end",
        options = FloxisInterstitialOptions(format = FloxisInterstitialFormat.DISPLAY_AND_VIDEO),
        listener = object : InterstitialAdUnitListener {
            override fun onAdLoaded(interstitialAdUnit: InterstitialAdUnit) {}
            override fun onAdDisplayed(interstitialAdUnit: InterstitialAdUnit) {}
            override fun onAdFailed(interstitialAdUnit: InterstitialAdUnit, exception: AdException) {}
            override fun onAdClicked(interstitialAdUnit: InterstitialAdUnit) {}
            override fun onAdClosed(interstitialAdUnit: InterstitialAdUnit) { loadInterstitial() }
            override fun onAdExpired(interstitialAdUnit: InterstitialAdUnit) { loadInterstitial() }
        },
    ).also { it.load() }
}

// At the placement. false means the ad is not ready.
val shown = interstitial?.show() == true

By default each interstitial auction asks for display and video together, and the higher bid wins. For one kind only, pass FloxisInterstitialFormat.DISPLAY or FloxisInterstitialFormat.VIDEO. To limit video length or skipping, pass FloxisInterstitialOptions(video = FloxisVideoOptions(maxDurationSeconds = 30)). For display, you can ask for a minimum creative size with minWidthPercent and minHeightPercent; set both or neither.

Rewarded#

import com.floxis.sdk.FloxisVideoOptions
import com.floxis.sdk.gam.FloxisGamRewardedAd
import org.prebid.mobile.api.exceptions.AdException
import org.prebid.mobile.api.rendering.RewardedAdUnit
import org.prebid.mobile.api.rendering.listeners.RewardedAdUnitListener
import org.prebid.mobile.rendering.interstitial.rewarded.Reward

private var rewarded: FloxisGamRewardedAd? = null

private fun loadRewarded() {
    rewarded?.destroy()
    rewarded = FloxisGamRewardedAd(
        activity = this,
        gamAdUnitId = "/1234567/app_rewarded",
        configId = "rewarded_coins",
        listener = object : RewardedAdUnitListener {
            override fun onAdLoaded(rewardedAdUnit: RewardedAdUnit) {}
            override fun onAdDisplayed(rewardedAdUnit: RewardedAdUnit) {}
            override fun onAdFailed(rewardedAdUnit: RewardedAdUnit, exception: AdException) {}
            override fun onAdClicked(rewardedAdUnit: RewardedAdUnit) {}
            override fun onUserEarnedReward(rewardedAdUnit: RewardedAdUnit, reward: Reward?) { grantReward() }
            override fun onAdClosed(rewardedAdUnit: RewardedAdUnit) { loadRewarded() }
            override fun onAdExpired(rewardedAdUnit: RewardedAdUnit) { loadRewarded() }
        },
        videoOptions = FloxisVideoOptions(maxDurationSeconds = 30), // optional
    ).also { it.load() }
}

// When the user asks for the reward. false means the ad is not ready.
val shown = rewarded?.show() == true

Floxis supports rewarded video on GAM. Rewarded display is not supported.

Showing full-screen ads#

  • Call show() after onAdLoaded. It returns true when the ad is going to be shown. It returns false before the ad has loaded, after it expired or failed, after destroy(), and after an earlier show(). After an expiry or failure, create a new object and load again.
  • onAdDisplayed confirms the ad is on screen. A Floxis bid in the load callback does not mean GAM chose Floxis.
  • Floxis.setEnabled(false) stops new Floxis auctions. It does not block an interstitial or rewarded ad that has already loaded.

Rewarded behaviour#

  • Grant the reward only in onUserEarnedReward. Its Reward can be null, so grant the amount your app sets for the placement rather than relying on reward.
  • Each rewarded ad that is shown sends onAdClosed exactly once. This holds whether GAM or Floxis rendered it, and whether the reward arrives before or after the close. The interstitial passes on each close event it gets.
  • Prebid Mobile 3.4.0 crashes at the end of a rewarded video whose inline VAST has neither a click-through link nor an end card. The SDK declines such a bid. GAM is then requested without Floxis key-values, and the warning FLX-204 … declined: rewarded VAST without ClickThrough or companion end card is logged. If GAM has no ad either, onAdFailed gets a No bids error that names the reason.

Reading the Floxis bid#

GAM objects have no Floxis auction listener. In a load callback, read Prebid's bid response from the ad:

override fun onAdLoaded(rewardedAdUnit: RewardedAdUnit) {
    val response = rewardedAdUnit.bidResponse
    Log.d("Floxis", "bid=${response?.winningBid?.price} hb_pb=${response?.targeting?.get("hb_pb")}")
}

For the banner, use bannerView.bidResponse. A null response means Floxis did not bid on this request.

Extra GAM request settings#

To add your own targeting or settings to the GAM request, pass requestConfiguration:

import org.prebid.mobile.eventhandlers.AdManagerRequestConfiguration

val banner = FloxisGamBannerAd(
    context = this,
    gamAdUnitId = "/1234567/app_banner",
    configId = "home_banner",
    sizes = listOf(FloxisSize(320, 50)),
    listener = bannerListener, // your BannerViewListener
    requestConfiguration = AdManagerRequestConfiguration { builder ->
        builder.addCustomTargeting("section", "home")
    },
)

builder is Google's AdManagerAdRequest.Builder. When personalised ads are off, the SDK adds npa=1 after your settings. The interstitial and rewarded constructors take the same requestConfiguration argument.

You are responsible for collecting consent and for your app's privacy disclosures. The **SDK privacy and consent** guide has the full detail, including what to declare in Play's Data safety form. For GAM:

  • Consent first. Finish your consent flow, for example Google's User Messaging Platform, before you load ads. Your CMP must write the standard IAB TCF, GPP and US-privacy keys to SharedPreferences. Prebid Mobile reads them there and forwards them with each Floxis auction.

  • Personalised ads off. Call Floxis.setPersonalizedAds(false) when the user declines personalised ads. The Floxis auction is skipped and the GAM request carries npa=1. Call it with true when consent is given.

  • Child-directed requests. Before each Floxis auction, the SDK copies Google Mobile Ads' tagForChildDirectedTreatment to Prebid, so set it the way you already do for Ad Manager:

    MobileAds.setRequestConfiguration(
        MobileAds.getRequestConfiguration().toBuilder()
            .setTagForChildDirectedTreatment(RequestConfiguration.TAG_FOR_CHILD_DIRECTED_TREATMENT_TRUE)
            .build(),
    )

    The auction still runs, and Floxis passes the flag to buyers as regs.coppa = 1. While it is set, no location is sent. When the tag is unspecified, a value you set yourself with TargetingParams.setSubjectToCOPPA() is kept.

    Floxis is not in Google's Families self-certified ads SDK program. Read the SDK privacy and consent guide before you use it in an app for children.

  • Device signals. Battery level, charging, battery saver, dark mode, ringer state, whether the media volume is muted (as video playbackmethod), and session depth and length are sent only when all of these hold: personalised ads are on; GDPR does not apply or the TCF string grants Purpose 1; neither the US-privacy string nor an applicable GPP US section records an opt-out of sale, sharing or targeted advertising.

  • Location. Sent only when all of these hold: your app already holds a location permission; shareLocation is on; the request is not child-directed; where GDPR applies, the TCF string opts in to precise geolocation (special feature 1); neither the US-privacy string nor an applicable GPP US section records an opt-out.

6. Add the app-ads.txt line#

Buyers that check app-ads.txt do not bid on the app without the Floxis line. Add it to app-ads.txt at the root of the developer website on the app's Play Store listing. The Floxis ads.txt, app-ads.txt and sellers.json Guide explains the format. You can also ask the dashboard assistant "what is my ads.txt line for Floxis".

7. Test before release#

  1. In a debug build, set debug = true and testMode = true in FloxisConfig.
  2. Use your own GAM test line items, or your real ones on a test ad unit. Google's sample ad units can show a Google test ad even when Floxis bids, so they cannot confirm a Floxis win.
  3. Run adb logcat | grep -E "Floxis|Prebid". At start-up you should see FLX-101 SUCCEEDED.
  4. Load each format and log bidResponse in onAdLoaded (see Reading the Floxis bid). When a bid carries an hb_pb value and your line item for that tier wins, the Floxis ad should appear.
  5. Check which line item served with Google's tools, for example Ad Inspector or Ad Manager's Delivery tools.
  6. Remove testMode and debug from the release configuration. A release build ignores testMode anyway.
  7. Tell your account manager your placement names and the date of the release build, so the first live traffic can be checked with you.

Test auctions are marked as test traffic. Some buyers don't bid on it, so a test build may see few Floxis bids.

Troubleshooting#

What you seeWhyWhat to do
onAdFailed with SDK internal error: GAM ad slot already loaded; create a new wrapper for each requestload() was called twice on one objectDestroy it and create a new object for each request
A Floxis bid in the load callback, but GAM shows another adGAM chose another line item, or no Floxis line item matchedCheck that a line item exists for that hb_pb value, targets the ad unit, is active, and does not target hb_format
The Floxis line item serves but nothing appearsThe creative does not send PrebidAppEvent, or the rewarded line item does not use the Prebid VAST URLUse the creatives in step 3 exactly
show() returns falseNot loaded yet, already shown, expired, failed or destroyedWait for onAdLoaded, or create a new object and load again
onAdFailed with No bids: … declined: rewarded VAST …A rewarded bid was declined (see Rewarded behaviour) and GAM had no adNothing to fix; the next load runs a new auction
No Floxis bids at allNot initialised, kill switch off, personalised ads off, or no demand for the requestWith debug on, check for FLX-101, and check enabled and personalizedAds
FLX-101 FAILED in the log (with debug on)Initialisation failed, for example with no networkNothing to do; it is retried on a later load, at most every 30 seconds (FLX-104)
FLX-103 in the logInitialisation hit an error inside the SDKSend the log line to your account manager
FLX-102 in the logenabled = false in FloxisConfigCall Floxis.setEnabled(true) when Floxis should run
FLX-105 in the logtestMode in a build that is not debuggableExpected in release builds
Gradle capability conflict naming floxis-sdk-gamfloxis-sdk-nextgen is in the same appUse one of the two
IllegalArgumentException when creating an objectBlank ad unit ID or configId, an empty size list, or an invalid FloxisConfig valueFix the argument

Floxis log lines use the tag Floxis and stable codes. With GAM you will see FLX-100 and FLX-101 (initialisation started and finished, with its status, when debug is on), FLX-102 (turned off in FloxisConfig), FLX-103 (an error inside initialisation), FLX-104 (retry), FLX-105 (test mode ignored) and FLX-204 (bid declined).

Formats#

FormatSupported
Banner (display)Yes
Interstitial: display, video, or both in one auctionYes
Rewarded videoYes
Rewarded display, rewarded interstitialNo
Native, app openNo
Banner video, instream videoNo

Licence, sample app and release notes#

The SDK ships as signed binaries from maven.floxis.tech, under the licence published there. Your account manager can share a sample app that loads each GAM format on your seat. What changed in each version is in the **SDK release notes** guide.