Forter — Building an Android APK Without a Laptop
Goal
Edit, build, and install the Forter APK using only a phone. Android apps cannot be compiled on a phone, so the build runs on GitHub Actions.
Loop:
- Edit code on GitHub web or the GitHub mobile app
- Commit — this triggers the workflow
- Build completes in about 3 minutes
- Tap the APK in Releases and install
Workflow
.github/workflows/build.yml:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
name: Build APK
on:
push:
branches: [dev, main]
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- name: JDK 21
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '21'
- name: Gradle cache
uses: gradle/actions/setup-gradle@v4
- name: Make gradlew executable
run: chmod +x ./gradlew
- name: Build
run: ./gradlew assembleDebug --no-daemon --stacktrace
- name: Version tag
id: tag
run: echo "name=build-$(TZ=Asia/Seoul date +%Y%m%d-%H%M)" >> $GITHUB_OUTPUT
- name: Upload release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.tag.outputs.name }}
name: Forter ${{ steps.tag.outputs.name }}
files: app/build/outputs/apk/debug/app-debug.apk
Runner
- Each run gets a fresh Ubuntu VM: 2+ cores, 16 GB RAM, SSD. Discarded when the job ends.
- Android SDK, JDK and other toolchains are preinstalled on the runner image.
- Public repositories: unlimited minutes. Private: 2,000 minutes per month.
gradle/actions/setup-gradle@v4caches dependencies between runs, so later builds are faster than the first.
Release instead of artifact
actions/upload-artifact delivers a ZIP. On a phone that requires a file manager and an extraction step before installing. Attaching the APK to a release makes it a single tap.
Publishing a release requires permissions: contents: write on the job. The injected token is read-only by default; without it the build succeeds and the final step fails with Resource not accessible by integration.
Executable bit on gradlew
A repository pushed from Windows does not carry the POSIX executable permission, and ./gradlew fails on the Linux runner with Permission denied.
Two fixes:
chmod +x ./gradlewas a workflow step- or fix it once in Git:
git update-index --chmod=+x gradlew
JDK version
Forter uses AGP 9.0.1 and Gradle 9.2.1, which require JDK 21. JDK 17, used in most Android CI examples, fails here. Check gradle/wrapper/gradle-wrapper.properties and the version catalog before copying a snippet.
Timestamps
Runners are on UTC. The first release was tagged build-20260912-1133 at 20:33 KST. TZ=Asia/Seoul in front of date produces a local tag.
Publishing the workflow on Jekyll
Liquid processes $ inside code blocks. Wrap the YAML in and , otherwise those lines render empty.
Installation
- Android warns “This app may be dangerous” for any APK not from the Play Store.
- Debug builds are signed with the SDK’s shared debug key, so there is no verified developer to display.
- The signing key is identical across builds, so each APK upgrades the previous install.
- APK size: 23 MB (debug build, Compose + Nordic BLE library).
Limitation
A compile error takes 3 minutes to surface instead of appearing immediately in the IDE. This pipeline fits small changes — polling intervals, log lines, flags to flip before a test drive. Writing new classes is still faster on a laptop.
Next: connecting to the OBD2 adapter over Bluetooth SPP and reading the first PID.