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#
| Item | Value |
|---|---|
Android minSdk | 24 |
App compileSdk | 35 |
| Java / Kotlin target | 17 |
| Google Mobile Ads | 25.5.0 or newer (play-services-ads, added for you) |
| Prebid Mobile | 3.4.0 with its GAM event handlers (added for you) |
| Cannot share an app with | floxis-sdk-nextgen (Google's Next-Gen Mobile Ads SDK) |
How it works#
- Floxis auction. When you call
load(), the SDK runs one Floxis auction for the placement (1.5 s deadline by default). - GAM request. If Floxis bids, the bid's key-values are added to the GAM ad request:
hb_pb(the price tier, for example1.20),hb_bidder=floxis,hb_env=mobile-app, andhb_size(for example320x50) when the bid has a size. - GAM decides. GAM weighs your Floxis line item for that tier against everything else that can serve.
- 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") }
}
}| Option | What it does |
|---|---|
timeoutMs | The longest the SDK waits for Floxis before GAM is requested without it (200–10,000 ms, default 1,500). |
debug | Turns on Floxis log lines (tag Floxis) and Prebid's debug logging. Warnings are logged either way. |
testMode | Marks 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. |
storeUrl | The store URL sent to buyers. Defaults to the store URL set on Prebid, then your app's Play Store page. |
shareLocation | Default 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#
- In Ad Manager, open Inventory › Key-values and create the key
hb_pbwith 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. - Optional: create the key
hb_bidderwith the valuefloxis. Add it to the line item targeting if other demand in the app also setshb_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#
- 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.
- Create one line item per price tier:
- Type: Price priority.
- Rate: the tier, for example
1.20for the1.20line 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_pbis the tier, for examplehb_pbis1.20.
- 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.
- 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.xmlThis 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, asgamAdUnitIdin 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.
Banner#
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.
Banner refresh#
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() == trueBy 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() == trueFloxis supports rewarded video on GAM. Rewarded display is not supported.
Showing full-screen ads#
- Call
show()afteronAdLoaded. It returnstruewhen the ad is going to be shown. It returnsfalsebefore the ad has loaded, after it expired or failed, afterdestroy(), and after an earliershow(). After an expiry or failure, create a new object and load again. onAdDisplayedconfirms 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. ItsRewardcan benull, so grant the amount your app sets for the placement rather than relying onreward. - Each rewarded ad that is shown sends
onAdClosedexactly 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 cardis logged. If GAM has no ad either,onAdFailedgets aNo bidserror 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.
5. Privacy and consent#
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 carriesnpa=1. Call it withtruewhen consent is given.Child-directed requests. Before each Floxis auction, the SDK copies Google Mobile Ads'
tagForChildDirectedTreatmentto 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 withTargetingParams.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;
shareLocationis 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#
- In a debug build, set
debug = trueandtestMode = trueinFloxisConfig. - 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.
- Run
adb logcat | grep -E "Floxis|Prebid". At start-up you should seeFLX-101 SUCCEEDED. - Load each format and log
bidResponseinonAdLoaded(see Reading the Floxis bid). When a bid carries anhb_pbvalue and your line item for that tier wins, the Floxis ad should appear. - Check which line item served with Google's tools, for example Ad Inspector or Ad Manager's Delivery tools.
- Remove
testModeanddebugfrom the release configuration. A release build ignorestestModeanyway. - 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 see | Why | What to do |
|---|---|---|
onAdFailed with SDK internal error: GAM ad slot already loaded; create a new wrapper for each request | load() was called twice on one object | Destroy it and create a new object for each request |
| A Floxis bid in the load callback, but GAM shows another ad | GAM chose another line item, or no Floxis line item matched | Check 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 appears | The creative does not send PrebidAppEvent, or the rewarded line item does not use the Prebid VAST URL | Use the creatives in step 3 exactly |
show() returns false | Not loaded yet, already shown, expired, failed or destroyed | Wait 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 ad | Nothing to fix; the next load runs a new auction |
| No Floxis bids at all | Not initialised, kill switch off, personalised ads off, or no demand for the request | With 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 network | Nothing to do; it is retried on a later load, at most every 30 seconds (FLX-104) |
FLX-103 in the log | Initialisation hit an error inside the SDK | Send the log line to your account manager |
FLX-102 in the log | enabled = false in FloxisConfig | Call Floxis.setEnabled(true) when Floxis should run |
FLX-105 in the log | testMode in a build that is not debuggable | Expected in release builds |
Gradle capability conflict naming floxis-sdk-gam | floxis-sdk-nextgen is in the same app | Use one of the two |
IllegalArgumentException when creating an object | Blank ad unit ID or configId, an empty size list, or an invalid FloxisConfig value | Fix 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#
| Format | Supported |
|---|---|
| Banner (display) | Yes |
| Interstitial: display, video, or both in one auction | Yes |
| Rewarded video | Yes |
| Rewarded display, rewarded interstitial | No |
| Native, app open | No |
| Banner video, instream video | No |
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.