Shopping List for Nextcloud ships to Google Play and F-Droid from the
same git tag, so both stores stay on the same versionCode.
Golden rule: one git tag = one release, shipped to both stores at the same
versionCode.
Play permanently forbids reusing a versionCode; F-Droid builds whatever
versionCode is in the tagged build.gradle.kts. Keep those equal and the two
stores never drift apart.
-
A JDK 17+. On Windows with no
javaon PATH, Android Studio's bundled JBR works:export JAVA_HOME="/c/Program Files/Android/Android Studio/jbr" -
keystore.properties+upload-keystore.jksat the repo root (both gitignored) for signing. Without them the release build is unsigned — fine for F-Droid (they sign with their own key) but not for a Play upload. -
For the automatic Play upload, four secrets on the GitHub repo, read by
.github/workflows/play-release.yml:Secret Holds UPLOAD_KEYSTORE_BASE64upload-keystore.jks, base64 encodedUPLOAD_KEYSTORE_PASSWORDthe keystore's storePasswordUPLOAD_KEY_PASSWORDthe uploadkey'skeyPasswordPLAY_SERVICE_ACCOUNT_JSONthe JSON key of a Google Cloud service account The service account needs the Google Play Android Developer API enabled in its Cloud project, and an invite in Play Console → Users and permissions with release rights on this app. The upload key is only the upload key (Google holds the app signing key), so it can be reset in Play Console if it ever leaks.
The app is translated on Crowdin,
in the same project as the web app. Crowdin can only watch one repo, so it works on
a copy of strings.xml in the web app's repo (android/), and
scripts/sync-translations.sh moves the text between the two. It expects the web
repo next to this one, or set WEB_REPO.
- After changing any text:
scripts/sync-translations.sh push, then commit the copied file in the web repo so Crowdin sees the new English. - Before a release: merge Crowdin's latest pull request in the web repo, then
scripts/sync-translations.sh pulland commit thevalues-*folders here.
A partly translated language is fine to ship, anything missing shows in English.
-
Bump the version in
app/build.gradle.kts:versionCode→ next integer, higher than anyversionCodeever pushed to Play (Play burns each one permanently).versionName→ the marketing version, e.g.0.5.0.
-
Write the changelog:
fastlane/metadata/android/en-US/changelogs/<versionCode>.txt— the filename is theversionCode(e.g.5.txt). User-facing, ≤500 chars (Play's "What's new" limit). Both Play and F-Droid read this. -
Build & check:
./gradlew :app:testDebugUnitTest :app:lintDebug ./gradlew :app:assembleRelease # runs lintVitalRelease + R8 shrinkingR8 is enabled for release builds. If R8 fails on missing classes, add the
-dontwarnlines it prints (app/build/outputs/mapping/release/missing_rules.txt) toapp/proguard-rules.pro, then rebuild. -
Commit the version bump + changelog.
-
Tag and push — this is the release trigger for F-Droid:
git tag -a vX.Y.Z -m "vX.Y.Z" git push origin vX.Y.Z git push github vX.Y.Z -
Play: pushing the tag to GitHub runs the Play release workflow. It checks the tag matches
versionName, runs the tests, builds and signs the bundle, and uploads it with the changelog as "What's new". The track it releases to isTRACKat the top of the workflow. Watch it withgh run watchor on the repo's Actions tab, and check the signing certificate it prints starts4C:ED:9F:40.To check the secrets without releasing anything, run the workflow by hand (Actions → Play release → Run workflow) with dry run ticked. It builds and signs the bundle and checks the Play credentials, from any branch.
-
If the workflow can't be used, build and upload by hand. Play needs the AAB, not the APK:
./gradlew :app:bundleRelease # -> app/build/outputs/bundle/release/app-release.aab "$JAVA_HOME/bin/keytool.exe" -printcert -jarfile \ app/build/outputs/bundle/release/app-release.aab | grep SHA256 # expect the upload cert fingerprint 4C:ED:9F:40:...Then Play Console → the app → the track → Create new release → upload the AAB.
-
F-Droid: nothing to do.
metadata/dev.otherworld.shoppinglist.ymlon fdroiddata usesUpdateCheckMode: TagsAutoUpdateMode: Version, so fdroidbot detects the new tag, appends a build entry for the sameversionCode, and builds from source. It publishes on F-Droid's own cycle (typically a day or few).
- Never upload an ad-hoc build to Play with a
versionCodethat has no matching git tag — Play jumps ahead of F-Droid. - Never push a version tag you aren't also uploading to Play — F-Droid jumps ahead of Play.
- If a
versionCodeis ever accidentally consumed on Play, bump past it everywhere (tag included) so the next real release lines up again.
0.4.0 = versionCode 4 = commit tagged v0.4.0 — live/queued on both stores.
0.3.0 (versionCode 3) shipped to Play only and was intentionally left
un-tagged.