Android on the Platform
Android builds need a 10 GB SDK, a matching JDK, and Gradle. On the Platform they run on a hosted Linux runner instead, signed with a keystore the Platform holds in custody, so you need no Android tooling on your own machine.
The app itself is the same Hotwire Native shell as the DIY track: a thin native wrapper around your deployed site.
Before you start
- Your app is deployed and in remote mode. Android builds are remote mode
only.
mode: remoteplus an absolutehttps://remote.urlis required, and the Platform refuses the submission without it. - You have connected the CLI with
every platform login. - A Google Play Console account ($25, one time), only if you are shipping to Play. Direct APK downloads need nothing from Google.
app:
name: Notes
bundle_id: com.example.notes
version: "1.4.0"
mode: remote
remote:
url: https://notes.example.com
build:
targets:
- android-arm64:play
platforms:
android:
bundle_id: com.example.notes.android # must be a valid applicationId
Android package names are stricter than bundle ids. An
applicationIdmust be two or more dot-separated segments of letters, digits and_, each starting with a letter, and no hyphens. The default bundle id derived from a multi-word app name is hyphenated (com.example.my-notes), so setplatforms.android.bundle_idyourself when yours would be. An invalid one is rejected up front rather than failing minutes into Gradle.
Pick this id carefully: Play ties an app to its package name permanently.
Two ways to ship
| Channel | Target | Artifact | Where it goes |
|---|---|---|---|
| Google Play | android-arm64:play |
.aab |
The Play track set on the release channel |
| Direct download | android-arm64:direct |
.apk |
Your update feed and a public download page |
You can ship both from one build: --target android-arm64:play,android-arm64:direct.
Upload credentials
Under Credentials โ New:
Android keystore (.jks), always
Create one with the JDK's keytool on any OS:
keytool -genkeypair -v -keystore upload.jks -alias upload -keyalg RSA -keysize 2048 -validity 10000
Answer the prompts, then upload upload.jks with the store password, key alias,
and key password you chose.
Keep your own copy. Google Play ties your app to this key. Lose it and you can never ship updates to that app again.
Google Play service account (.json), Play only
Entirely web-based:
- In Google Cloud โ Service Accounts, create a service account, then Keys โ Add key โ JSON and download it.
- In Play Console โ Users and permissions, invite that service account's email and grant it Release access to your app.
Upload the JSON. The Platform checks it's a real service-account key with a usable private key before accepting it.
Build it
every platform build --target android-arm64:play
every platform build --target android-arm64:direct
Either waits for a hosted Linux runner, builds the shell against your
deployed URL, signs it with your keystore, and downloads the finished artifact
into dist/. The play channel produces an .aab (Play's upload format, which
cannot be installed on a phone); direct produces an installable .apk.
The app's versionCode is derived from app.version, so 1.2.3 becomes
10203 and bumping the version bumps the code Play compares against. Set
EVERY_ANDROID_VERSION_CODE if your pipeline numbers builds instead of
versioning them.
After the build: Google Play
Promote the succeeded build in the dashboard (Promote, pick a channel). The
Platform then uploads the .aab to Play itself, over the Play Developer API.
The service-account key never leaves the server and never reaches a runner. It
opens an edit, uploads the bundle, points the track at the new version code, and
commits. Nothing is visible to testers until that commit lands, so a failure
part-way through changes nothing.
Which track? Each release channel carries a Google Play track: internal,
alpha, beta, or production. New channels start on internal. Set it on
the channel's row on the app page before promoting. Release notes from the
promote form travel with the release as the track's notes.
Play's own rules still apply, and its refusals come back verbatim on the release:
- The app's first release must be made in the Play Console by hand. The API cannot create one.
- The service account needs the Release apps to testing tracks permission.
- The version code must be higher than every build already uploaded.
After the build: direct downloads
Promote an android-arm64:direct build and the Platform publishes the signed
.apk, then serves it from a public install page:
https://platform.rubyeverywhere.com/d/<org>/<app>/<channel>
The page shows the version, size, and SHA-256, a Download APK button, a QR code so a tester can open the page on the phone itself, and instructions for Android's "install unknown apps" prompt (which every sideloaded app triggers the first time). The channel key is always in the URL, so a link you hand out keeps pointing at that channel.
With a bucket connected, the same release also
writes a signed update feed, so installed copies update themselves. Without one,
the Platform hosts the .apk and the page works the same way, with no feed to
update from.
When a build is refused
- "Android builds are remote mode only". Set
mode: remoteineverywhere.yml. - "remote mode needs remote.url set to an absolute http(s) URL". The shell has nothing to point at without it.
- "can't be an Android applicationId". A hyphen or a single-segment id. Set
platforms.android.bundle_id. - Missing credentials. The keystore for either channel, plus the Play
service account for
play.
What's the same as DIY
Tabs, icons, colors, permissions, native extensions, and the
bridge behave as on the
DIY Android page, including platforms.android.target_sdk
when you need to get ahead of a Play deadline. The Platform changes where the
build runs, not what gets built.