Building the app is the easy half. Getting it through both Apple’s and Google’s review pipelines, repeatedly, without breaking something each time, is where most of the friction lives. Here’s the routine I’ve settled into.
Version everything from one source
Keep version in pubspec.yaml as the single source of truth and let your CI derive the build number:
version: 1.4.0+42 # marketing version + build numberA mismatched build number is the most common reason an otherwise-fine upload gets rejected. Automating it removes the human error entirely.
Signing, once
- Android: store the keystore and its passwords in CI secrets, never in the repo. A leaked keystore means you can never update that app again.
- iOS: use a dedicated CI signing identity and managed provisioning. Fighting Xcode’s automatic signing on a build server is a losing battle.
The review gotchas
- Permissions need a reason. Every
NSCameraUsageDescription-style string must explain why. Vague text gets flagged. - Sign in with Apple. If you offer any third-party login on iOS, Apple requires their option too.
- Account deletion. Both stores now expect an in-app path to delete an account if you can create one.
Make releases boring
The goal is for a release to be a non-event: tag the commit, let CI build and upload to both TestFlight and the Play internal track, smoke-test, promote. Boring releases are reliable releases.