|
| 1 | +# Mobile deployment — current state and what's missing |
| 2 | + |
| 3 | +Written 2026-07-14, alongside the v3.0.0 desktop release. Desktop (Linux / |
| 4 | +Windows / macOS) now builds and publishes from `.github/workflows/release.yml`. |
| 5 | +**Mobile does not ship from CI at all.** This is the working notes for that. |
| 6 | + |
| 7 | +OpenView is already live on both stores, so nothing here is greenfield: |
| 8 | + |
| 9 | +- App Store (iPhone / iPad / Apple Silicon Mac) — `id1667747246`, `com.protocentral.openview` |
| 10 | +- Google Play — `com.protocentral.openview` |
| 11 | + |
| 12 | +## Where mobile deployment actually stands |
| 13 | + |
| 14 | +**iOS — manual, from a developer Mac.** `ios.sh` runs `flutter build ios |
| 15 | +--release --no-codesign` then `fastlane ios beta`, and `ios/fastlane/Fastfile` |
| 16 | +does `build_app` → `upload_to_testflight`. That works, but: |
| 17 | + |
| 18 | +- It is not wired into any workflow. Every release is somebody's laptop. |
| 19 | +- `ios/fastlane/Appfile` authenticates as a personal Apple ID |
| 20 | + (`ashwinkw@ieee.org`). CI needs an **App Store Connect API key** (issuer ID, |
| 21 | + key ID, `.p8`) instead — an Apple ID login with 2FA cannot run unattended. |
| 22 | +- There is no `match`/certificate management, so signing assets live only on |
| 23 | + that one Mac. |
| 24 | + |
| 25 | +**Android — nothing.** No fastlane, no Play publishing, no signed build. |
| 26 | +`distribution/whatsnew/whatsnew-en-US` is a leftover from a |
| 27 | +`r0adkll/upload-google-play` setup that no longer exists in this repo — nothing |
| 28 | +reads it today. |
| 29 | + |
| 30 | +## Fixed already (found while getting v3.0.0 out) |
| 31 | + |
| 32 | +- **A Java keystore was committed** — `android/akw-newkey`, in the initial |
| 33 | + commit, in a public repo. Removed from HEAD and `*.jks`/`key.properties`/`*.p12` |
| 34 | + are now gitignored. **The key is still public in git history and in the 14 |
| 35 | + forks — it must be rotated.** See "Key rotation" below. |
| 36 | +- **`flutter build apk --release` was broken in CI**: an undefined GitHub |
| 37 | + Actions secret is exported as an *empty string*, so |
| 38 | + `System.getenv("KEYSTORE_BASE64") != null` was true with no secret set, and |
| 39 | + Gradle wrote a zero-byte keystore. Now tests for blank. |
| 40 | +- **`flutter build apk --release` OOM'd** — `JetifyTransform` ran out of heap at |
| 41 | + `-Xmx1536M`. Now `-Xmx4g`, and `android.enableJetifier=false` (every plugin |
| 42 | + here is AndroidX already). *Unverified — no Android build has run since.* |
| 43 | + |
| 44 | +## Key rotation (do this first) |
| 45 | + |
| 46 | +The exposed keystore is almost certainly your Play **upload** key, not the app |
| 47 | +signing key — if Play App Signing is on, Google holds the signing key and the |
| 48 | +upload key is resettable. |
| 49 | + |
| 50 | +1. Play Console → Test and release → Setup → **App integrity** → App signing. |
| 51 | +2. If Play App Signing is enabled: **Request upload key reset**, upload a new |
| 52 | + upload certificate. Old key stops working; users are unaffected. |
| 53 | +3. If it is *not* enabled, the leaked key signs your published app — that is the |
| 54 | + bad case, and it needs an app signing key upgrade (new installs only). |
| 55 | + |
| 56 | +A backup of the removed keystore is at `~/.protocentral-keys/akw-newkey.jks` on |
| 57 | +Ashwin's machine. Treat it as burned. |
| 58 | + |
| 59 | +## To deploy Android from CI |
| 60 | + |
| 61 | +1. New release keystore, kept out of the repo. Secrets: `KEYSTORE_BASE64`, |
| 62 | + `KEY_ALIAS`, `KEY_PASSWORD`, `STORE_PASSWORD`. (The `key.properties` plumbing |
| 63 | + in `android/app/build.gradle.kts` already reads these.) |
| 64 | +2. `flutter build appbundle --release` for Play; `flutter build apk --release` |
| 65 | + only if you also want a sideload asset on the GitHub release. |
| 66 | +3. Play service account JSON → `PLAY_SERVICE_ACCOUNT_JSON` secret → |
| 67 | + `r0adkll/upload-google-play` with `track: internal`, promoting manually. |
| 68 | + `distribution/whatsnew/` becomes the `whatsNewDirectory` again. |
| 69 | + |
| 70 | +Note: a GitHub-released APK can never upgrade a Play-installed app in place — |
| 71 | +Play delivers APKs signed with Google's app signing key. That is expected, not a |
| 72 | +bug, and it is independent of which keystore we choose. |
| 73 | + |
| 74 | +## To deploy iOS from CI |
| 75 | + |
| 76 | +1. App Store Connect API key → secrets (`ASC_KEY_ID`, `ASC_ISSUER_ID`, |
| 77 | + `ASC_KEY_P8`). Replace the `apple_id` auth in `Appfile`. |
| 78 | +2. Signing assets into CI — `fastlane match` (private certs repo) is the usual |
| 79 | + answer; otherwise import a distribution `.p12` + provisioning profile from |
| 80 | + secrets, like the macOS job does. |
| 81 | +3. `macos-latest` runner, `flutter build ipa` (or fastlane `build_app`), then |
| 82 | + `upload_to_testflight`. |
| 83 | + |
| 84 | +## Bugs to fix before the next mobile release |
| 85 | + |
| 86 | +- **`NSLocalNetworkUsageDescription` is missing from `ios/Runner/Info.plist`.** |
| 87 | + `lib/transport/wifi_service.dart` is a raw TCP client to a LAN `host:port`. On |
| 88 | + iOS 14+ that requires the local-network permission, and without the usage |
| 89 | + string iOS never prompts and refuses the connection — **Wi-Fi mode is probably |
| 90 | + broken on iOS today**. Worth reproducing on a device first. |
| 91 | +- `NSAppTransportSecurity.NSAllowsArbitraryLoads = true` is a blanket ATS |
| 92 | + exemption and invites App Store review questions. If it is only there for the |
| 93 | + LAN TCP transport, `NSAllowsLocalNetworking` is the narrower answer. |
| 94 | +- `ITSEncryptionExportComplianceCode` is set to the string `"Yes"`, which is not |
| 95 | + a compliance code. With `ITSAppUsesNonExemptEncryption = false` it should just |
| 96 | + be removed. |
| 97 | +- iOS deployment target is inconsistent: `Podfile` says 13.1, the Runner target |
| 98 | + says 15.3, `Info.plist` `LSMinimumSystemVersion` says 13.0. |
| 99 | +- `AndroidManifest.xml` declares `ACCESS_FINE_LOCATION` / `ACCESS_COARSE_LOCATION` |
| 100 | + *and* removes the `-sdk-23` variants, while `BLUETOOTH_SCAN` is already |
| 101 | + `neverForLocation`. If location is not actually needed, dropping it simplifies |
| 102 | + the Play data-safety declaration. |
| 103 | +- `WRITE_EXTERNAL_STORAGE` / `READ_EXTERNAL_STORAGE` are declared with no |
| 104 | + `maxSdkVersion` — Play flags this on modern targets. |
| 105 | +- Cosmetic: `MainActivity.kt` declares `package com.protocentral.openview` but |
| 106 | + lives in `.../kotlin/com/protocentral/healthypiconnect/`. Stale |
| 107 | + `MARKETING_VERSION = 2.0.15` / `CURRENT_PROJECT_VERSION = 85` in |
| 108 | + `ios/Runner.xcodeproj` are harmless (Info.plist uses `$(FLUTTER_BUILD_NAME)` / |
| 109 | + `$(FLUTTER_BUILD_NUMBER)`, so Flutter's pubspec version wins). |
| 110 | + |
| 111 | +## F-Droid |
| 112 | + |
| 113 | +Customers have asked for it. It is feasible, and the licence is not the problem |
| 114 | +— the repo is MIT, there is no telemetry, no ads, no Firebase. |
| 115 | + |
| 116 | +**The one blocker is `geolocator`.** It is declared in `pubspec.yaml` but |
| 117 | +**imported nowhere in `lib/`**, and `geolocator_android` pulls in |
| 118 | +`com.google.android.gms:play-services-location` — a proprietary dependency |
| 119 | +F-Droid will not build against. Deleting the dependency removes the blocker, |
| 120 | +shrinks the APK, and drops Play Services entirely. That is a one-line change |
| 121 | +plus a `flutter pub get`, and it is worth doing regardless of F-Droid. |
| 122 | + |
| 123 | +After that, F-Droid inclusion is a metadata exercise, not an engineering one: |
| 124 | + |
| 125 | +1. Verify `flutter build apk --release` works from a clean checkout (needs the |
| 126 | + Gradle heap fix above to be confirmed). |
| 127 | +2. Submit a metadata YAML merge request to |
| 128 | + [fdroiddata](https://gitlab.com/fdroid/fdroiddata) with a build recipe using |
| 129 | + the Flutter srclib, pinned to our Flutter version, plus |
| 130 | + `UpdateCheckMode: Tags` and `AutoUpdateMode: Version` so new git tags are |
| 131 | + picked up automatically. |
| 132 | +3. Expect review round-trips on the first MR. |
| 133 | + |
| 134 | +Effort: roughly a day for the first submission, then near-zero per release |
| 135 | +(F-Droid builds from tags). Two things to tell users up front: F-Droid signs |
| 136 | +with **its own key**, so the F-Droid build and the Play build cannot upgrade each |
| 137 | +other — switching stores means uninstall/reinstall — and F-Droid builds land |
| 138 | +days after the Play release, because they build and sign on their own |
| 139 | +infrastructure. |
0 commit comments