Remote minimum-version gate
Remote minimum-version gate
A server-controlled floor on the running build. Any build whose versionCode is
below the remote minimum runs normally without ads: the Mobile Ads SDK is
never initialised, no app-open ad is requested, no banner or interstitial is
created. The app itself is never blocked — there is no update wall.
Design decided by Dries, 23 Sept 2026 (Asana 1218577128615630): the gate refuses to initialise the ads SDK below the minimum; it does not refuse to run the app. A build that never requests an ad cannot generate an ad-policy violation, and a mistaken remote value costs “no ads for a session”, never “no app” for a paying customer. In-app updates use FLEXIBLE, not IMMEDIATE. Remote Config failures fail OPEN — ads run as normal. The first version of this branch had a full-screen update wall and an IMMEDIATE update; both were removed on 25 Sept 2026.
What this cannot do: code shipped in the first build that carries the gate cannot run on a device still sitting on an older build. It prevents FUTURE stranding; it is not a remedy for users already on old builds.
Why it exists
AdMob judged five review cycles against a stale 4.1.5 build that was never on Play. This gate makes that impossible to repeat: raise the remote floor and every copy that carries the gate and is below it stops starting the ads SDK on its next launch after the fetch, so such a binary can never again be reviewed with ads live.
The Remote Config key
| Key | min_supported_version_code |
| Type | Number (int) — a versionCode / Android build number |
| In-app default | 0 |
| Meaning | The lowest versionCode still allowed to start the ads SDK. A build is supported when currentVersionCode >= min_supported_version_code; below it the app runs without ads. |
The value is a versionCode, not a versionName. Read it from
docs/RELEASES.md (Play Console: expand the release → Versiecodes), never
from a human-facing release name. iOS and Android share this one key; because
the same versionName+versionCode has shipped as different code across platforms
(see docs/RELEASES.md), set it to the number you actually want to floor.
Fail-open by design
The read fails open. Any failure — offline, fetch timeout, exception, a
platform call that never answers, or a key that was never set — resolves to the
in-app default 0, which is <= every real build, so ads run as normal. The
gate only ever switches ads off when someone deliberately raises the remote
value. Its job is to stop KNOWN-BAD versions, not unknown ones.
Mechanically (lib/services/version_gate.dart):
RemoteMinVersion.firebase()callsfetchAndActivate()with aminimumFetchIntervalof 12 h (the cached value is served in between, so startup is not gated on the network after the first fetch) and an 8 s fetch timeout.RemoteMinVersion.get()adds an overall 10 s timeout and a try/catch → returns0on any error or hang.VersionGateis a pure, injectable class (currentVersionCodevsminSupportedVersionCode) withisSupported/isBelowMinimum.- The read is not awaited before
runApp, so it can never delay the first frame.AdsVersionGate(lib/widgets/ads_version_gate.dart) wraps the app, renders it immediately and unconditionally, and resolves the read afterrunApp: supported →onAdsAllowedopens the version flag onAdsController; below the minimum → the flag stays closed and the Android flexible update is offered. AdsControllerhas a third flag beside entitlement and consent:_versionAllowsAds, written only viaupdateVersionGate().canServeAdsis the AND of all three, and every ad surface readscanServeAds, so below the minimum no surface requests an ad andinitialize()is a no-op.main()constructs the controller withversionAllowsAds: false; the gate opens it.
Android in-app update
When the build is below the minimum on a Play-installed copy, VersionUpdateFlow
(lib/services/version_update_flow.dart) attempts a one-shot FLEXIBLE
in_app_update flow: the update downloads in the background while the app
stays usable, and is installed (which restarts the app) only when the user next
leaves it. IMMEDIATE is not used — it is a blocking full-screen takeover,
reserved for a genuine compliance stop. On failure, on non-Android, when no
update is available, or when Play does not allow a flexible update, nothing
happens: the app keeps running, only ads stay off.
iOS has no in-app update API and gets no prompt. Secondaire’s App Store numeric id, for any future non-blocking update nudge, is 1611881585.
Operating rule — READ THIS BEFORE YOU TOUCH THE VALUE
Raise
min_supported_version_codeto the previous production versionCode only while an AdMob review is pending, and lower it again once the review clears. Never raise it as a habit.
Every user on a build below the value loses ads (and so stops earning) until they update; the app keeps working for them. A value left high after a review switches ads off on perfectly good builds. Treat raising it as an incident action with an explicit rollback, not a routine knob.
How to set it in the Firebase console
- Firebase console → project verbomatique-secondaire → Run → Remote Config.
- Add parameter (or edit the existing one):
- Parameter name:
min_supported_version_code - Data type: Number
- Default value: the
versionCodeyou want to floor (e.g.118). Use0to disable the gate entirely.
- Parameter name:
- Publish changes. Clients pick it up on their next
fetchAndActivate(within the 12 hminimumFetchInterval; a fresh install fetches on first launch). - To lift the gate after a review, set the value back to
0and publish.
No platform conditions are needed — one value floors both platforms. If you ever want to floor iOS and Android independently, add a Remote Config condition on platform and give the key two values; the client reads the single resolved value.