Floxis Android SDK guide#
The Floxis Android SDK adds Floxis demand to an Android app that already monetises with AdMob. It is
a thin layer over the open-source Prebid Mobile SDK, installed as one Gradle dependency. It works with both
Google SDKs: the classic Google Mobile Ads SDK (play-services-ads) and the Next-Gen Mobile Ads SDK.
Before you start, set up an endpoint for the app on the Supply page with the integration type Floxis SDK. When you open the saved endpoint, its integration panel (under Integration type) shows:
- the seat and region for this app, already filled into the setup code below;
- the AdMob setup sheet: one AdMob Custom Event per price tier, to copy or download as CSV;
- the AdMob price tier check, which shows tiers that get bids but never win in AdMob.
This guide covers version 0.2.0, the current release. Where the next release (0.2.1) changes behaviour, the text says so.
Requirements#
Classic AdMob (floxis-sdk) | Next-Gen (floxis-sdk-nextgen) | |
|---|---|---|
| Artifact | tech.floxis:floxis-sdk | tech.floxis:floxis-sdk-nextgen |
| Google SDK | play-services-ads 25.5.0 or newer | ads-mobile-sdk 1.5.0 or newer |
| Prebid Mobile | 3.4.0 and its AdMob adapters 3.4.0, added for you | Same |
| Minimum Android | API 24 (minSdk = 24) | API 24 |
| Compile against | API 35 (compileSdk = 35) | API 35 |
| Build tools | Android Gradle Plugin 8 or newer; the SDK's classes target Java 17 | Same |
| Kotlin | Not needed for Java apps. Kotlin apps: the SDK is built with Kotlin 2.3 | Same |
| Formats | Banner, interstitial (display, video or both), rewarded video | Same |
Native, app open and rewarded interstitial ads are not supported.
How it works#
- Floxis auction. Before each AdMob load, the SDK runs one auction for the placement (1.5 s deadline by default).
- AdMob load. If Floxis wins, the price tier (for example
hb_pb = 0.70) is added to the AdMob request. - Custom Event renders. AdMob's waterfall reaches the Custom Event for that price tier, and the Prebid adapter shows the Floxis ad.
If Floxis doesn't bid, or doesn't answer in time, AdMob loads exactly as it does today. Floxis takes part in your AdMob waterfall at the eCPM of each Custom Event. It does not take part in AdMob bidding.
1. Add the SDK#
Add the SDK repository to settings.gradle.kts. The exclusiveContent filter makes Gradle look there
only for tech.floxis packages, and everything else still comes from Google and Maven Central:
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:
implementation("tech.floxis:floxis-sdk:0.2.0")Gradle brings in Prebid Mobile 3.4.0, its AdMob adapters 3.4.0 and Google Mobile Ads 25.5.0 or newer
from Google and Maven Central, so you don't add them yourself. Minimum Android SDK is 24, and the app compiles
against API 35 (compileSdk = 35), which Google Mobile Ads 25.5 needs for a minified release build. Imports are
com.floxis.sdk.*.
Apps on Google's Next-Gen Mobile Ads SDK#
If the app uses the Next-Gen SDK (com.google.android.libraries.ads.mobile.sdk:ads-mobile-sdk 1.5.0 or newer)
instead of play-services-ads, add implementation("tech.floxis:floxis-sdk-nextgen:0.2.0") instead. Never add both.
Use the Next-Gen setup below. The AdMob setup sheet and Custom Events do not change.
Pass Next-Gen's MobileAds.initialize an Activity, or call it from Application.onCreate. If it gets an application
context from an activity that is already running, interstitial and rewarded ads load but do not show.
Manifest#
The SDK adds the INTERNET and ACCESS_NETWORK_STATE permissions itself. It asks for no other permission.
For the classic SDK, keep the AdMob app ID in your manifest, as Google requires:
<meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="ca-app-pub-XXXX~YYYY" />Next-Gen takes the app ID in InitializationConfig instead (see Next-Gen setup).
The advertising ID permission (com.google.android.gms.permission.AD_ID) comes from Google's SDK, not from
Floxis. If your app is child-directed and you remove it, see SDK privacy and consent.
R8 and ProGuard#
You don't need any keep rules. The SDK's rules ship inside the artifact and keep its public API and the Prebid AdMob adapters that AdMob loads by class name. Crash stacks that pass through the SDK keep their line numbers, so send them to your account manager as they are.
2. Initialise after MobileAds.initialize#
Use the seat and region from your endpoint's integration panel:
MobileAds.initialize(this) {
Floxis.initialize(
applicationContext,
FloxisConfig(seat = "YOUR_SEAT", region = "us-e", timeoutMs = 1500),
) { status, description -> Log.d("Floxis", "init $status $description") }
}In Java, FloxisConfig takes its values in order (seat, region, timeoutMs, debug, enabled, personalizedAds,
testMode, storeUrl, shareLocation):
MobileAds.initialize(this, initializationStatus ->
Floxis.initialize(
getApplicationContext(),
new FloxisConfig("YOUR_SEAT", "us-e", 1500),
(status, description) -> Log.d("Floxis", "init " + status + " " + description)));| Option | What it does |
|---|---|
seat | Your seat from the integration panel. 1–128 characters of letters, digits, ., _ and -. |
region | Your region from the integration panel. Default us-e. |
timeoutMs | The longest the SDK waits for Floxis before AdMob loads without it (200–10,000 ms, default 1,500). |
debug | Turns on the SDK's detail log lines under the tag Floxis (codes FLX-xxx) and Prebid Mobile's debug log. Warnings log even when it is off. It never changes what is sent, so it is safe in a release build. |
testMode | Marks auctions as test requests (test: 1). It only takes effect in a debuggable build, and a release build ignores it. It does not guarantee that an ad which renders is left out of billing. |
enabled / Floxis.setEnabled() | Kill switch. false stops all Floxis auctions at once, for example from your remote config, and refuses to show a Floxis-won full-screen ad that has already loaded. |
personalizedAds / Floxis.setPersonalizedAds() | false skips Floxis bidding and sends npa=1 to AdMob. Update it when consent changes. |
storeUrl | The store URL sent to buyers. Default: the store URL you set on Prebid Mobile, otherwise the Google Play page for your package. |
shareLocation | Default true: sends the device's last known location when the app already has location permission. |
How initialisation behaves:
- You can call
Floxis.initializemore than once and from any thread. The listener is always called once, on the main thread, withSUCCEEDED,SERVER_STATUS_WARNING,FAILEDorDISABLED. - If the SDK can't start within 12 seconds, the listener gets
FAILED. A failed start is not permanent: the next ad load tries again, at most once every 30 seconds. - Ads you load before the SDK has started still load from AdMob, without a Floxis auction.
- A value outside the limits above throws
IllegalArgumentExceptionwhen you create theFloxisConfig.
Consent and privacy#
Your app and its consent tool (CMP) decide what the user agreed to. The SDK passes those signals to the auction. The full rules, and what to declare in Google Play's Data safety form, are in SDK privacy and consent.
- TCF, GPP and US privacy. Your CMP must write the standard IAB TCF, GPP and US-privacy keys to
SharedPreferences, and Prebid Mobile reads them from there and adds them to every auction. - Personalised ads. With
personalizedAds = false, the SDK runs no Floxis auction and addsnpa=1to the AdMob request. - Children (COPPA). Floxis does not block child-directed traffic. It passes your app's COPPA flag to the
auction as
regs.coppa, and buyers apply their own rules.- When you set Google's child-directed tag (
tagForChildDirectedTreatmentin the Google request configuration), the SDK copies it into every auction, as true or false. - If you leave Google's tag unspecified, the SDK sends whatever you set on Prebid Mobile with
TargetingParams.setSubjectToCOPPA(true)(importorg.prebid.mobile.TargetingParams).
- When you set Google's child-directed tag (
- Device and session signals (from 0.2.1). Battery level, charging, battery saver, dark mode, ringer state,
whether the media volume is muted (as video
playbackmethod), impression depth and session length are added to an auction only when personalised ads are on, GDPR does not apply or the TCF string grants Purpose 1, and neither the US privacy string nor an applicable GPP US section records an opt-out of sale, sharing or targeted advertising.
Location: the SDK never asks for a location permission. It sends a location only when the app already holds
ACCESS_FINE_LOCATION or ACCESS_COARSE_LOCATION, and never for a child-directed request, when GDPR applies
without consent to precise geolocation (TCF special feature 1), or when the US privacy string records an opt-out.
From 0.2.1, an opt-out in an applicable GPP US section also withholds it.
If you send location, declare it in the Play Console's Data safety form.
3. Set up AdMob mediation#
For each ad unit that should get Floxis demand:
In AdMob, open Mediation, create a mediation group for the format and platform (or open your existing one), and add the ad unit.
Add one Custom Event per row of your endpoint's AdMob setup sheet. Pick the platform (Android) and ad format in the panel, then copy the rows or download them as CSV. Each row gives the label, class name, parameter and eCPM:
- Banner:
org.prebid.mobile.admob.PrebidBannerAdapter - Interstitial and video interstitial:
org.prebid.mobile.admob.PrebidInterstitialAdapter - Rewarded:
org.prebid.mobile.admob.PrebidRewardedAdapter
For example, the banner row for $1.25 has the label
<your brand> Banner 1.25, the parameter{"hb_pb":"1.25"}and the eCPM1.25.- Banner:
Create every row of the sheet. AdMob matches the parameter exactly, so a missing tier means a winning Floxis bid at that price can't show. When you change the endpoint's price tiers, save the endpoint, export the sheet again and update AdMob to match.
Banner ad units: turn off AdMob's automatic refresh. The Floxis banner refreshes itself and runs a new auction each time.
After traffic starts, open the AdMob price tier check on the endpoint. A tier marked Step missing in AdMob gets bids but never wins, which almost always means its Custom Event is missing from the ad unit.
4. Load ads#
configId is your name for the placement (for example home_banner, level_end_interstitial). Floxis
reports by placement, so give each placement its own name and keep it the same across app versions.
Banner#
val adView = AdView(this).apply {
adUnitId = "ca-app-pub-XXXX/BANNER"
setAdSize(AdSize.BANNER)
}
container.addView(adView)
val banner = FloxisBannerAd(
adView = adView,
configId = "home_banner",
sizes = listOf(FloxisSize(320, 50)),
refreshIntervalSeconds = 60, // 30..120, 0 turns refresh off
listener = { outcome -> Log.d("Floxis", "$outcome") },
)
banner.load()
override fun onPause() { banner.pause(); adView.pause(); super.onPause() }
override fun onResume() { super.onResume(); adView.resume(); banner.resume() }
override fun onDestroy() { banner.destroy(); adView.destroy(); super.onDestroy() }Java:
AdView adView = new AdView(this);
adView.setAdUnitId("ca-app-pub-XXXX/BANNER");
adView.setAdSize(AdSize.BANNER);
container.addView(adView);
FloxisBannerAd banner = new FloxisBannerAd(
adView, "home_banner", Collections.singletonList(new FloxisSize(320, 50)), 60,
outcome -> Log.d("Floxis", outcome.toString()));
banner.load();- The first size is the main one; add more to the list if the slot can show them.
- The banner refreshes only while the app is in the foreground, the screen is on and at least half the banner is visible. Otherwise it waits for the next interval. A value below 30 or above 120 is raised or lowered to fit.
- The banner stops by itself when the activity that holds it is destroyed.
banner.pause(),resume()anddestroy()don't touch theAdView, so call theAdView's own methods too, as above.- The
listenergets the Floxis auction result when the AdMob request is sent, not when AdMob answers. For AdMob's own load events, set anAdListeneron theAdViewas usual.
Interstitial#
FloxisInterstitialAd.load(
context = this,
adMobAdUnitId = "ca-app-pub-XXXX/INTERSTITIAL",
configId = "level_end_interstitial",
callback = object : FloxisLoadCallback<FloxisInterstitialAd> {
override fun onAdLoaded(ad: FloxisInterstitialAd) { interstitial = ad }
override fun onAdFailedToLoad(error: LoadAdError, auction: AuctionOutcome) { }
},
)
when (interstitial?.show(activity)) {
FloxisShowResult.EXPIRED, FloxisShowResult.DISABLED -> reloadInterstitial()
else -> Unit
}Java:
FloxisInterstitialAd.load(this, "ca-app-pub-XXXX/INTERSTITIAL", "level_end_interstitial",
new FloxisLoadCallback<FloxisInterstitialAd>() {
@Override public void onAdLoaded(FloxisInterstitialAd ad) { interstitial = ad; }
@Override public void onAdFailedToLoad(LoadAdError error, AuctionOutcome auction) { }
});By default each interstitial auction asks for display and video together, and the higher bid wins. For one
kind only, pass options = FloxisInterstitialOptions(FloxisInterstitialFormat.DISPLAY) (or VIDEO). Video length
and skipping can be limited with FloxisInterstitialOptions(video = FloxisVideoOptions(maxDurationSeconds = 30)).
In Java, pass new FloxisInterstitialOptions(FloxisInterstitialFormat.DISPLAY) as the fifth argument.
load returns a FloxisLoadHandle. Call cancel() on it, for example in onDestroy, if the screen goes away
before the ad loads. No callback fires after that.
Pass your FullScreenContentCallback to show(activity, contentCallback). onAdFailedToLoad fires only when the
AdMob load fails, which includes an ordinary AdMob no-fill.
Rewarded#
FloxisRewardedAd.load(this, "ca-app-pub-XXXX/REWARDED", "rewarded_main", callback)
// optional: videoOptions = FloxisVideoOptions(maxDurationSeconds = 30)
rewarded.show(activity, { reward -> grant(reward) })Java (in Java, videoOptions comes after the request factory, so pass FloxisRequestFactory.DEFAULT to set it):
FloxisRewardedAd.load(this, "ca-app-pub-XXXX/REWARDED", "rewarded_main", callback,
FloxisRequestFactory.DEFAULT, new FloxisVideoOptions(null, 30));
rewarded.show(activity, reward -> grant(reward));Rewarded ads are video only. A rewarded video bid that has no click-through and no end card is
declined before it reaches AdMob, and the auction reports NO_BID (log code FLX-204).
Add your own targeting#
To add your own AdMob targeting, pass a request factory. The SDK calls it for every load, refreshes included:
banner.load { AdRequest.Builder().setContentUrl("https://example.com/article") }FloxisInterstitialAd.load and FloxisRewardedAd.load take the same factory as requestFactory. When
personalised ads are off, the SDK sets the AdMob network extras to npa=1, which replaces any AdMobAdapter
extras bundle your factory added.
Show results and errors#
show returns a FloxisShowResult:
| Result | Meaning | What to do |
|---|---|---|
SHOWN | The ad is showing. | Nothing. |
ALREADY_SHOWN | This ad was already shown once. | Load a new one. |
DESTROYED | You called destroy() on it. | Load a new one. |
EXPIRED | Floxis won, but the bid ran out of time (usually after 5 minutes). | Load a new one. |
DISABLED | Floxis won, but the kill switch is now on. | Load a new one. |
When show refuses, your content callback's onAdFailedToShowFullScreenContent still fires, with the error domain
com.floxis.sdk and the code from FloxisShowError (ALREADY_SHOWN 1, DESTROYED 2, EXPIRED 3, DISABLED 4).
isFloxisWinner tells you whether Floxis served a loaded ad.
Every load callback carries an AuctionOutcome with status, priceBucket (the hb_pb tier on a win),
latencyMs and detail. The status is one of BID, NO_BID, TIMEOUT, ERROR, SKIPPED_DISABLED,
SKIPPED_NO_CONSENT (personalised ads off) or SKIPPED_NOT_INITIALIZED. What each one means, and how to fix it,
is in SDK testing and troubleshooting.
Once the SDK is set up, a failure inside an auction never throws into your code: AdMob loads without Floxis, and a
warning is logged. Invalid arguments do throw IllegalArgumentException when you create the object: a blank
configId, an empty size list, a size that is not positive, or video options where the minimum is above the maximum.
Next-Gen setup#
Use these imports instead of the classic Google Mobile Ads imports. The examples run inside an Activity;
container is the banner's view group.
import android.util.Log
import com.floxis.sdk.*
import com.floxis.sdk.nextgen.*
import com.google.android.libraries.ads.mobile.sdk.MobileAds
import com.google.android.libraries.ads.mobile.sdk.banner.AdSize
import com.google.android.libraries.ads.mobile.sdk.banner.AdView
import com.google.android.libraries.ads.mobile.sdk.common.LoadAdError
import com.google.android.libraries.ads.mobile.sdk.initialization.InitializationConfigInitialise Google on a background thread, then initialise Floxis on main. Replace YOUR_ADMOB_APP_ID
with the app ID from AdMob:
val config = InitializationConfig.Builder("YOUR_ADMOB_APP_ID").build()
Thread({
MobileAds.initialize(this, config) {
runOnUiThread {
Floxis.initialize(
applicationContext,
FloxisConfig(seat = "YOUR_SEAT", region = "us-e", timeoutMs = 1500),
) { status, description -> Log.d("Floxis", "init $status $description") }
}
}
}, "gma-init").start()After initialisation, load each format with its Next-Gen class. Pass the banner's AdMob ad unit ID
and size to FloxisNextGenBannerAd, rather than setting them on the view:
val adView = AdView(this)
container.addView(adView)
val banner = FloxisNextGenBannerAd(
adView = adView,
adMobAdUnitId = "ca-app-pub-XXXX/BANNER",
adSize = AdSize.BANNER,
configId = "home_banner",
sizes = listOf(FloxisSize(320, 50)),
refreshIntervalSeconds = 60,
)
banner.load()
override fun onPause() { banner.pause(); super.onPause() }
override fun onResume() { super.onResume(); banner.resume() }
override fun onDestroy() { banner.destroy(); adView.destroy(); super.onDestroy() }FloxisNextGenBannerAd also takes a loadListener (FloxisNextGenBannerListener), called on the main thread after
every banner load, refreshes included. banner.destroy() stops refresh but leaves the AdView to you, so destroy
the AdView too, as above. The SDK destroys each banner that a refresh replaces.
For banner events such as impressions and clicks, pass your BannerAdEventCallback as the eventCallback
argument. The SDK puts it on each banner as soon as Google returns it, so the first impression is never missed.
Its events arrive on Google's background thread.
For full-screen ads, keep the loaded ad until the placement is ready, then call show:
var interstitial: FloxisNextGenInterstitialAd? = null
FloxisNextGenInterstitialAd.load(
context = this,
adMobAdUnitId = "ca-app-pub-XXXX/INTERSTITIAL",
configId = "level_end_interstitial",
callback = object : FloxisNextGenLoadCallback<FloxisNextGenInterstitialAd> {
override fun onAdLoaded(ad: FloxisNextGenInterstitialAd) { interstitial = ad }
override fun onAdFailedToLoad(error: LoadAdError, auction: AuctionOutcome) { }
},
)
when (interstitial?.show(activity)) {
FloxisShowResult.EXPIRED, FloxisShowResult.DISABLED -> reloadInterstitial()
else -> Unit
}
var rewarded: FloxisNextGenRewardedAd? = null
FloxisNextGenRewardedAd.load(
context = this,
adMobAdUnitId = "ca-app-pub-XXXX/REWARDED",
configId = "rewarded_main",
callback = object : FloxisNextGenLoadCallback<FloxisNextGenRewardedAd> {
override fun onAdLoaded(ad: FloxisNextGenRewardedAd) { rewarded = ad }
override fun onAdFailedToLoad(error: LoadAdError, auction: AuctionOutcome) { }
},
)
rewarded?.show(activity, { reward -> runOnUiThread { grant(reward) } })Next-Gen differs from classic in a few places:
- To add your own targeting, pass a
FloxisNextGenRequestCustomizertoload. It receives Google's request builder for each load. When personalised ads are off, the SDK replaces the Google extras bundle withnpa=1. destroy()on a Next-Gen interstitial or rewarded ad also destroys Google's ad.- The reward listener is Google's own and is called on Google's background thread, so post UI work to the main thread, as above.
Floxis initialisation and load callbacks, including Next-Gen banner auction and load listeners, arrive on the
main thread. Classic SDK callbacks also arrive on main. Next-Gen full-screen event callbacks passed to show
(InterstitialAdEventCallback or RewardedAdEventCallback) are installed on Google's ad unchanged and arrive on
Next-Gen's background thread; post UI work to the main thread with runOnUiThread in those callbacks.
5. Use with other Floxis SDKs#
Available from the next release; contact your Floxis account manager for early access.
From 0.2.1, an app that also mediates through Google Ad Manager, AppLovin MAX or Unity LevelPlay can add the matching Floxis artifact next to this one. See In-app SDK (Google Ad Manager), In-app SDK (AppLovin MAX) and In-app SDK (Unity LevelPlay).
With floxis-sdk (classic) | With floxis-sdk-nextgen |
|---|---|
| Google Ad Manager, AppLovin MAX or Unity LevelPlay | AppLovin MAX or Unity LevelPlay |
floxis-sdk-nextgencan't share an app withfloxis-sdkor the Google Ad Manager artifact, because Next-Gen can't share an app withplay-services-ads. From 0.2.1, Gradle stops such a build with an error that names the pair.- The AdMob, Google Ad Manager and AppLovin MAX artifacts share one setup, so call
Floxis.initializeonce, with one seat and region. - Unity LevelPlay doesn't read
FloxisConfigorFloxis.setPersonalizedAds. Give it the same seat and region in its network settings, and send consent changes to LevelPlay as well. - Keep every
tech.floxisartifact on the same version. From 0.2.1, if they differ, Gradle raises the older ones to the newest version in the build.
6. Add the app-ads.txt line#
Buyers that check app-ads.txt will 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. Ask the dashboard assistant "what is my
ads.txt line for Floxis" for the exact line for your account. Where the file goes and how to check it is in the
Floxis ads.txt, app-ads.txt and sellers.json Guide.
7. Test before release#
- In a debug build, set
debug = trueandtestMode = true. - Register your phone as an AdMob test device, so the ads AdMob serves itself are test ads. Never click live ads on your own app.
- Run
adb logcat -s Floxis. You should seeFLX-101 SUCCEEDEDat start-up, then one auction line (FLX-200) per ad load. - Check that an ad Floxis won shows through the AdMob Custom Event:
isFloxisWinneristrue, and AdMob's Ad Inspector shows the Custom Event as the ad source. Some buyers don't bid on test requests, so a test build may see few Floxis wins. A no-bid in testing is normal. Test requests are not guaranteed to be left out of billing, so keep test traffic small. - Before release, turn
debugoff if you don't want the extra log lines. A release build ignorestestModeand logsFLX-105if it is still set. - Tell your account manager your placement names and the date of the release build, so the first live traffic can be checked with you.
| Log code | Meaning |
|---|---|
FLX-101 | Start-up finished, with its status: SUCCEEDED, SERVER_STATUS_WARNING or FAILED (debug only). |
FLX-102 | enabled = false, so the SDK did not start. |
FLX-103 / FLX-104 | Start-up threw an error / a failed start is being retried. |
FLX-105 | testMode ignored because the build is not debuggable. |
FLX-200 / FLX-201 | Auction result / auction skipped. Both are debug only. |
FLX-202 | A winning bid expired before the AdMob load. |
FLX-203 | Auction failed. |
FLX-204 | A rewarded video bid was declined. |
FLX-400 | show refused, with the reason. |
The full list, and what to do about each one, is in SDK testing and troubleshooting.
More documentation#
- SDK privacy and consent: the signals the SDK reads and sends, children's apps, and the Data safety form.
- SDK testing and troubleshooting: every log code and auction status, and common problems.
- SDK API reference: every public class and method.
- SDK release notes: what changed in each version.
- In-app SDK (iOS, AdMob): the same setup for iOS apps.
- In-app SDK (Google Ad Manager), In-app SDK (AppLovin MAX) and In-app SDK (Unity LevelPlay): apps that serve through Ad Manager or mediate with MAX or LevelPlay.
Licence and sample app#
The SDK ships as signed binaries from maven.floxis.tech, under the licence published there. Floxis.SDK_VERSION
gives the version in your build. Your account manager can share a sample app that loads each ad format on your
seat, and is your contact for support.