The first production Android upload feels bigger than the code. The binary is ready. The store listing is not. This is what broke, what I fixed, and what I now treat as non-negotiable before every release.
The problem
Local installs hid the real blockers: signing mismatches, Play Console policy checks, and crash reports that only appear after a closed testing track. A Flutter build that worked on my device failed review for missing data-safety answers and an incomplete content rating questionnaire.
What I set up before the next upload
- A dedicated upload key and documented keystore recovery path — not a laptop-only secret
- A Play Console checklist: privacy policy URL, data safety form, screenshots at required sizes, feature graphic
- Crash reporting wired before open testing so day-one crashes are visible
- A versionCode / versionName discipline so hotfixes never collide with an in-flight review
Architecture of the release path
CI builds a signed App Bundle. The same pipeline runs smoke tests on a physical device farm entry when budget allows. Closed testing validates login, offline open, and the primary happy path. Production promote is a button, not a manual sideload ritual.
The important split: build identity (keystore + applicationId) stays stable; feature flags absorb risky UI experiments so a bad experiment is not a forced store rollback.
What went wrong the first week
Crashlytics showed a null on a deep-link route that only QA opened from a cold start. Store listing screenshots were cropped wrong on tablet form factors. Support email in the listing bounced because the mailbox was not monitored.
None of those are Flutter bugs. They are product-ops bugs. Treat them as part of the ship.
Takeaway
Publishing is a system: signing, policy, observability, and a rollback story. Once that system exists, the next app — company or client — is mostly repeating the checklist, not reinventing the ritual.