Releasing mobile apps
On the web, a bad deploy is fixed by deploying the previous version. Mobile has no such escape. Every release passes through a store, users update when they choose, and once a version is on a phone you cannot take it back. Good mobile release practice is about limiting the damage a bad build can do.
Store review
Apple reviews every build before it reaches the App Store, and Google Play reviews apps too. Review usually takes hours to a few days, and a rejection adds another round. So a fix is never minutes away.
Submit early, and give reviewers a working demo account if your app needs a login.
Versions and build numbers
Each platform has two numbers.
| Aspect | User-facing version | Build number |
|---|---|---|
| iOS | CFBundleShortVersionString, e.g. 4.12.0 |
CFBundleVersion |
| Android | versionName, e.g. 4.12.0 |
versionCode, an integer |
The build number is what the stores use to tell uploads apart. Google Play requires versionCode to increase with every upload; App Store Connect requires each build of a version to have a new, higher build number. The simplest rule that satisfies both is to increase it on every build. Generate it in CI so no one bumps it by hand.
Staged rollouts
Don’t give a new version to everyone at once.
- Google Play’s staged rollout releases to a percentage of users that you choose and increase over time. You can halt it if something goes wrong.
- The App Store’s phased release spreads automatic updates over seven days. You can pause it, but users who update manually from the store still get the new version.
Watch crash and error rates at each step before widening. A problem found at 1% of users is an incident; at 100% it is an outage.
Feature flags and remote config
Because you can’t roll back a binary, ship risky code switched off and turn it on from the server. Remote config delivers flags and settings at runtime, through a service such as Firebase Remote Config or your own endpoint.
{
"min_supported_version": "4.8.0",
"flags": {
"new_checkout": { "enabled": true, "percent": 10 },
"offline_sync_v2": { "enabled": false }
}
}
A flag gives you a kill switch: if the new checkout misbehaves, turn it off in seconds without a store review. Cache the last config on the device and use safe defaults when it can’t be fetched. Remove flags once a feature has fully launched.
Forced updates
Sometimes old versions must stop working: a security flaw, or an API you can no longer keep alive. Build the mechanism before you need it, because it only works in versions that already contain it.
The app compares its version with a minimum supported version from remote config at launch. Below it, show a blocking screen that links to the store. On Android, the Play In-App Updates API can show an update flow inside the app. Use it sparingly: some users can’t update because their OS is too old.
Crash reporting and stability
Ship every build with crash reporting, such as Firebase Crashlytics or Sentry, and upload the symbol files, dSYMs on iOS and R8 mapping files on Android, so stack traces are readable.
The main stability metric is crash-free users: the share of users who had no crash in a period. Crash-free sessions is the related per-session figure. Track them per app version, alongside ANRs and hangs, and compare each new version with the previous one during rollout. Google Play’s Android vitals also reports crash and ANR rates.
Plan for no rollback
When a release is bad, your options are:
- halt the staged rollout or pause the phased release;
- turn off the feature with a flag;
- fix forward with a new build and an expedited review if needed;
- as a last resort, raise the minimum supported version.
Each must be prepared before release.
Release checklist
- Build numbers generated by CI and always increasing.
- New features behind remote flags, with safe defaults.
- A minimum supported version check already shipping in the app.
- Crash reporting enabled and symbols uploaded for this build.
- Staged rollout, with a named person watching metrics at each step.
- A decided threshold for halting, agreed before launch.