~ knotless Search/
↑↓ move↵ open⇧↵ all results

grain.social/grain-android

1 collaborator
0
1
main
96 lines · 3.8 KB Raw History
# Releasing

## One-time setup

### 1. Generate an upload key

Google holds the real app signing key under Play App Signing and re-signs every
release. What you generate here is only the *upload* key — if it's lost, Play
support can reset it, which is not true of the app signing key.

```bash
keytool -genkeypair -v \
  -keystore upload-keystore.jks \
  -alias upload \
  -keyalg RSA -keysize 2048 -validity 10000
```

Then create `keystore.properties` in the repo root:

```properties
storeFile=upload-keystore.jks
storePassword=<store password>
keyAlias=upload
keyPassword=<key password>
```

Both files are gitignored. Keep a backup somewhere other than this machine —
without them you can still recover, but only by going through Play support.

Release builds without `keystore.properties` are left unsigned rather than
failing, so a fresh clone and CI can still compile the release variant.

### 2. Play Console

- Create the app. The package name **`social.grain` is permanent** once the
  first build is uploaded and cannot be changed afterwards.
- Under **Setup → App signing**, opt into Play App Signing (the default for new
  apps) and let Google generate the app signing key.
- Fill in the **Data safety** form. The app collects the account handle and DID,
  the photos a user chooses to publish, and their EXIF when the poster leaves
  camera data enabled. All of it goes to the user's own PDS via grain.social.
  It requests `INTERNET` and, for the story composer, `CAMERA`; photos taken
  there are only ever sent when the user posts them.
- Content rating questionnaire, target audience, and a privacy policy URL are
  all required before any track can go live.

Note for personal developer accounts created after 13 Nov 2023: production is
locked until a closed test has run with at least 12 testers opted in for 14
continuous days. Check **Testing → Closed testing** — a requirement card there
means the rule applies.

## Cutting a release

Bump `versionCode` and `versionName` in `app/build.gradle.kts`. `versionCode`
must increase with every upload, even for a build that never ships.

```bash
./gradlew bundleRelease
```

The bundle lands at `app/build/outputs/bundle/release/app-release.aab`. Upload
that, not the APK — Play requires the App Bundle format.

To sanity-check a release build on a device first, the APK variant can be signed
with the debug key and installed locally:

```bash
./gradlew assembleRelease
zipalign -f 4 app/build/outputs/apk/release/app-release-unsigned.apk /tmp/aligned.apk
apksigner sign --ks ~/.android/debug.keystore --ks-pass pass:android \
  --ks-key-alias androiddebugkey --key-pass pass:android \
  --out /tmp/release-test.apk /tmp/aligned.apk
adb install -r /tmp/release-test.apk
```

This is worth doing at least once per dependency bump. R8 strips
kotlinx.serialization's generated `$$serializer` classes without the keep rules
in `proguard-rules.pro`, and the failure is release-only: every network decode
throws and the feed simply comes back empty.

## Store assets

- Icon: `store/play-store-icon-512.png` (regenerate with
  `python3 tools/generate_icons.py`).
- Feature graphic: `store/play-feature-graphic-1024x500.png` (regenerate with
  `python3 tools/generate_feature_graphic.py`).
- Screenshots: `store/play-screenshot-*.png`. Capture with `adb exec-out
  screencap -p > shot.png`, then fit them to Play's rules with `python3
  tools/prepare_screenshots.py shot.png --out store/` — a raw capture is 2.06:1,
  which is past the 2:1 Play accepts, and the script pads each to 1080x1920
  rather than cropping the app's own chrome away.

  Sign in on an emulator rather than using a phone: a locked screen returns a
  black `screencap` and blocks instrumented tests. Use your own galleries — the
  feed shows other people's photographs, which aren't yours to put in a store
  listing.