Floxis Maven/Documentation

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)
Artifacttech.floxis:floxis-sdktech.floxis:floxis-sdk-nextgen
Google SDKplay-services-ads 25.5.0 or newerads-mobile-sdk 1.5.0 or newer
Prebid Mobile3.4.0 and its AdMob adapters 3.4.0, added for youSame
Minimum AndroidAPI 24 (minSdk = 24)API 24
Compile againstAPI 35 (compileSdk = 35)API 35
Build toolsAndroid Gradle Plugin 8 or newer; the SDK's classes target Java 17Same
KotlinNot needed for Java apps. Kotlin apps: the SDK is built with Kotlin 2.3Same
FormatsBanner, interstitial (display, video or both), rewarded videoSame

Native, app open and rewarded interstitial ads are not supported.

How it works#

  1. Floxis auction. Before each AdMob load, the SDK runs one auction for the placement (1.5 s deadline by default).
  2. AdMob load. If Floxis wins, the price tier (for example hb_pb = 0.70) is added to the AdMob request.
  3. 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)));
OptionWhat it does
seatYour seat from the integration panel. 1–128 characters of letters, digits, ., _ and -.
regionYour region from the integration panel. Default us-e.
timeoutMsThe longest the SDK waits for Floxis before AdMob loads without it (200–10,000 ms, default 1,500).
debugTurns 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.
testModeMarks 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.
storeUrlThe store URL sent to buyers. Default: the store URL you set on Prebid Mobile, otherwise the Google Play page for your package.
shareLocationDefault true: sends the device's last known location when the app already has location permission.

How initialisation behaves:

  • You can call Floxis.initialize more than once and from any thread. The listener is always called once, on the main thread, with SUCCEEDED, SERVER_STATUS_WARNING, FAILED or DISABLED.
  • 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 IllegalArgumentException when you create the FloxisConfig.

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 adds npa=1 to 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 (tagForChildDirectedTreatment in 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) (import org.prebid.mobile.TargetingParams).
  • 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:

  1. In AdMob, open Mediation, create a mediation group for the format and platform (or open your existing one), and add the ad unit.

  2. 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 eCPM 1.25.

  3. 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.

  4. 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.

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() and destroy() don't touch the AdView, so call the AdView's own methods too, as above.
  • The listener gets the Floxis auction result when the AdMob request is sent, not when AdMob answers. For AdMob's own load events, set an AdListener on the AdView as 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:

ResultMeaningWhat to do
SHOWNThe ad is showing.Nothing.
ALREADY_SHOWNThis ad was already shown once.Load a new one.
DESTROYEDYou called destroy() on it.Load a new one.
EXPIREDFloxis won, but the bid ran out of time (usually after 5 minutes).Load a new one.
DISABLEDFloxis 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.InitializationConfig

Initialise 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 FloxisNextGenRequestCustomizer to load. It receives Google's request builder for each load. When personalised ads are off, the SDK replaces the Google extras bundle with npa=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 LevelPlayAppLovin MAX or Unity LevelPlay
  • floxis-sdk-nextgen can't share an app with floxis-sdk or the Google Ad Manager artifact, because Next-Gen can't share an app with play-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.initialize once, with one seat and region.
  • Unity LevelPlay doesn't read FloxisConfig or Floxis.setPersonalizedAds. Give it the same seat and region in its network settings, and send consent changes to LevelPlay as well.
  • Keep every tech.floxis artifact 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#

  1. In a debug build, set debug = true and testMode = true.
  2. 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.
  3. Run adb logcat -s Floxis. You should see FLX-101 SUCCEEDED at start-up, then one auction line (FLX-200) per ad load.
  4. Check that an ad Floxis won shows through the AdMob Custom Event: isFloxisWinner is true, 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.
  5. Before release, turn debug off if you don't want the extra log lines. A release build ignores testMode and logs FLX-105 if it is still set.
  6. 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 codeMeaning
FLX-101Start-up finished, with its status: SUCCEEDED, SERVER_STATUS_WARNING or FAILED (debug only).
FLX-102enabled = false, so the SDK did not start.
FLX-103 / FLX-104Start-up threw an error / a failed start is being retried.
FLX-105testMode ignored because the build is not debuggable.
FLX-200 / FLX-201Auction result / auction skipped. Both are debug only.
FLX-202A winning bid expired before the AdMob load.
FLX-203Auction failed.
FLX-204A rewarded video bid was declined.
FLX-400show 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.