I've shipped a handful of Android apps over the last couple of years and the part that always slowed me down wasn't the code. It was the last mile. Signing, bundling, filling in the same store metadata I've filled in twenty times before, staring at the Play Console wondering which tab the language translations hide behind this week.
This is a short write up of the release flow I actually settled on. Nothing fancy. No CI pipeline. Just a sane local setup that lets me push a new version without losing half a day.
Step 1: stop relying on memory for keystore stuff
The thing that bit me the most was signing config drift. I'd set up signing once, forget about it, come back three months later and have no idea which variant was actually signed for release.
Fix: keep a single keystore.properties file (not in git) and reference it in build.gradle. Then pin it to the release variant only. Once this is set once, it's set forever.
signingConfigs {
release {
def props = new Properties()
props.load(new FileInputStream(rootProject.file("keystore.properties")))
storeFile file(props['storeFile'])
storePassword props['storePassword']
keyAlias props['keyAlias']
keyPassword props['keyPassword']
}
}
Simple. But I kept forgetting to pin it to buildTypes.release.signingConfig. If your upload to Play ever silently fails or shows a weird "invalid signature" error, check that first.
Step 2: build an AAB, not an APK
I know this sounds obvious in 2026 but I still see people trying to upload APKs. Play wants the App Bundle. One command:
./gradlew bundleRelease
Output lives in app/build/outputs/bundle/release/. That's the file you want.
Step 3: the metadata problem
This is the part nobody warns you about. The Play Console asks you for the same 30 fields every time. Screenshots, feature graphic, short description, full description, content rating, target audience, ads, data safety form, translations, etc. Each release.
If you change literally one screenshot dimension, expect an email the next morning saying your release is held for review.
My current solution is to template the whole thing. I keep a folder for each app with:
metadata/en-US/short_description.txtmetadata/en-US/full_description.txtmetadata/en-US/images/phone/*.pngmetadata/en-US/changelogs/default.txt
Then I either upload manually in a fixed order (my printed checklist is a lifesaver) or use a tool to push it.
Step 4: the tool I actually use now
Full disclosure, I built a desktop app for this because the manual loop was eating my Saturdays. It's called IOn Emit. It reads the metadata folder, handles the AAB upload, and pushes translations. If that sounds useful: https://theionproject.com/ionemit. It's freemium.
If you'd rather stay manual, I honestly understand. The command line plus a good checklist gets you 80 percent of the way there. The one thing I'd push for either way is that your release process lives in version control, not in your head.
Step 5: the boring stuff that matters
A few habits that saved me from shipping bugs to prod:
- Always bump
versionCodeby at least 1, never reuse. - Tag the commit as
v1.4.2the second you upload. If you ever need to reproduce that exact build, you'll thank yourself. - Keep a
RELEASE_NOTES.mdwith what's different in each version. This is what goes into the Play Store changelog. No rewriting it on the fly. - Do a dry run on internal testing track before production. Yes every time.
Wrapping up
The goal isn't to automate everything. The goal is to make the release feel boring. If pushing a new version feels dramatic, something is wrong with your process, not your willpower.
If this was useful, I write more about solo dev stuff over at https://theionproject.com. Happy to answer questions on signing config gotchas, I've hit most of them.
Top comments (0)