There Is No Undo on a Phone
How a Staged Rollout Really Behaves
| Rule | What Google says |
|---|---|
| Scope | "Your update reaches only a percentage of your users, which you can increase over time." |
| First releases | "Staged rollouts can only be used for app updates, not when publishing an app for the first time." |
| Growth | "Your app's staged rollout percentage won't increase automatically." |
| Halting | "No additional users will receive the app version in your existing staged rollout." |
| Resuming | "When you halt and then resume the rollout of your release, you'll be affecting the same set of users." |
| Fixing | "If you find an issue with your existing app bundle, create and roll out a new release with a fixed app bundle." |
Let Vitals, Not the Calendar, Promote a Release
| Stage | Rollout | Promote only if |
|---|---|---|
| Canary | 1% | Crash and ANR rates at or below the previous release, no new top crash cluster |
| Early | 5% | Same, plus no device model trending toward the 8% per-device threshold |
| Broad | 20% | Same, plus ratings and uninstalls flat against the last release |
| Majority | 50% | Same, plus backend error rates and support tickets flat |
| Complete | 100% | Same, plus nothing new in pre-launch or accessibility reports |
Automate the Ladder with the Publishing API
edits.insert opens one ("when you first create an edit, the edit is a copy of the current deployed state of the app"), edits.tracks.update changes the release on a track, and edits.commit publishes. Two warnings from the docs belong in your pipeline's README. First: "If anyone makes any changes to the app through the Google Play Console while you have an edit in progress, your edit is discarded." Second, edits.commit defaults to CANCEL_IN_REVIEW_AND_SUBMIT, "which will cancel the changes in review and then send all the changes for publishing", so an automated promotion can silently cancel a listing change a colleague submitted that morning. Set it explicitly.
A staged release is a track update with a userFraction and a status. The tracks guide uses exactly this shape: start an "inProgress" release at a fraction, set it to "halted" to stop, and "if you later decide to resume a halted release you can do so by setting its status back to 'inProgress'." Per the API reference, the fraction must satisfy 0 < fraction < 1 and "can only be set when status is 'inProgress' or 'halted'"; completing the rollout is a status change to "completed", not a fraction of 1.
Most teams drive this from Gradle. Gradle Play Publisher describes itself as "Android's unofficial release automation Gradle Plugin" that can do "anything from building, uploading, and then promoting your App Bundle or APK to publishing app listings and other metadata." Read its README before depending on it: it states that "issues are ignored, but pull requests are not," which is a maintenance posture to plan around, not a reason to avoid it.// build.gradle.kts: Gradle Play Publisher
play {
track.set("production")
userFraction.set(0.01) // canary stage; 1.0 is rejected
updatePriority.set(2) // in-app update priority, 0..5
releaseStatus.set(ReleaseStatus.IN_PROGRESS)
}
// CI promotes the same artifact up the ladder once vitals gates pass:
// ./gradlew promoteArtifact --update production --user-fraction .05
// ./gradlew promoteArtifact --update production --user-fraction .2
//
// Equivalent Publishing API body for edits.tracks.update:
// { "releases": [{ "versionCodes": ["4210"], "userFraction": 0.05, "status": "inProgress" }] }
// Halt: same release, "status": "halted"
// Complete: same release, "status": "completed" (no userFraction)Fix Forward, Fast: Update Priority and Managed Publishing
inAppUpdatePriority when the release is created, because it "can not be updated once the release is rolled out." The guide adds the detail that makes it work for incident response: "the returned priority takes into account the inAppUpdatePriority for all app version codes between the installed version and latest available version." Ship the fix at priority 5 and every user on the bad build sees it as critical, whatever version they skipped from.
Finally, decouple approval from go-live. With managed publishing on, approved changes wait for you instead of being "published automatically as soon as they're reviewed and approved by Google." Note the exception Google lists: "increasing an existing staged roll-out to 100%" is not held back, so the final promotion is always immediate.suspend fun maybeUpdate(activity: ComponentActivity, launcher: ActivityResultLauncher<IntentSenderRequest>) {
val manager = AppUpdateManagerFactory.create(activity)
val info = manager.appUpdateInfo.await()
if (info.updateAvailability() != UpdateAvailability.UPDATE_AVAILABLE) return
// Priority is set per release in Play (0..5) and aggregated across skipped versions.
val type = if (info.updatePriority() >= 4 && info.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)) {
AppUpdateType.IMMEDIATE // fix-forward for a bad build: block until updated
} else {
AppUpdateType.FLEXIBLE // routine update: download in the background
}
manager.startUpdateFlowForResult(info, launcher, AppUpdateOptions.newBuilder(type).build())
}The Release Checklist That Assumes You Will Ship a Bug
changesInReviewBehavior set explicitly.
4. Keep a fix-forward lane warm. A version-code bump, a release branch and a priority-5 in-app update path that you have rehearsed.
5. Halt first, investigate second. A halt is reversible and resumes the same cohort. Another hour at 20% is not reversible for the people who install in that hour.
The teams that look calm during a bad release are not the ones with fewer bugs. They are the ones whose bug reached 1% of phones instead of all of them.Key Takeaways
- 1Android blocks versionCode downgrades, so users who installed a bad build can only be fixed by a new build with a higher versionCode.
- 2Play can now halt a fully rolled-out release and serve the previous one to new users, but it cannot remove the bad build from phones that already have it.
- 3Staged rollouts never advance on their own, apply only to updates, and resume to the same user cohort after a halt.
- 4Gate each promotion on vitals: Play shows hourly crash and ANR rates for a new release’s first days, and the bad-behavior thresholds are 1.09%, 0.47% and 8% per device.
- 5Automate the ladder with the Publishing API or Gradle Play Publisher, and set changesInReviewBehavior explicitly; the default cancels changes in review.
- 6Ship fix-forward releases with in-app update priority 5 so users on the bad build see them as critical.
Frequently Asked
Can I roll back an Android app update on Google Play?
Not for users who already installed it. Android blocks versionCode downgrades, so the fix is a new release with a higher versionCode. Play can halt a release, including one at 100%, so new users get the previous version.
Does a staged rollout increase automatically?
No. Google states the percentage will not increase automatically. Promote it yourself in Play Console or through the Publishing API.
What userFraction completes a rollout through the API?
None. userFraction must be between 0 and 1 exclusive. Complete a rollout by setting the release status to completed.
Ready to architect your next Android app?
ANDROID-ARCHITECT generates production-ready Kotlin code, architecture blueprints, and CI/CD configurations from plain-language descriptions. Start building for free.