MyFamilyTracker
Real-time family location sharing — Firebase Realtime DB for sub-second propagation, WorkManager + ForegroundService for OS-compliant background collection, geofencing via Google Maps API.
A practical GitHub Actions setup for Android — build, test, lint, and Play Store publish on a solo dev workflow. No fluff, no enterprise-scale overhead.
On this page
CI/CD for Android has a reputation for being complicated to set up. Here's a practical workflow for solo developers — covering build, test, lint, and Play Store publish.
name: Android CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
cache: gradle
- name: Run lint
run: ./gradlew lint
- name: Run unit tests
run: ./gradlew testDebugUnitTest
- name: Build debug APK
run: ./gradlew assembleDebug
- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results
path: app/build/reports/tests/This runs on every push and PR. If lint or tests fail, the workflow fails — you know before merging.
Without caching, a fresh dependency download adds 3–5 minutes per run:
- name: Cache Gradle
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-The cache key hashes your Gradle files, so it invalidates correctly when dependencies change.
Never commit keystore files. Store everything in GitHub Secrets under your repo settings. Read all credentials from environment variables in your
build.gradle.ktsThe workflow decodes the keystore at build time from a base64-encoded secret, builds the AAB, and the file is gone when the runner exits. Nothing sensitive ever touches the repo.
- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.2'
bundler-cache: true
- name: Publish to Play Store (internal track)
run: bundle exec fastlane supply
env:
SUPPLY_JSON_KEY_DATA: ${{ secrets.PLAY_STORE_JSON_KEY }}
SUPPLY_TRACK: internal
SUPPLY_AAB: app/build/outputs/bundle/release/app-release.aabPLAY_STORE_JSON_KEYDon't publish to Play Store on every push. Use a separate workflow on tags:
name: Release
on:
push:
tags:
- 'v*'
jobs:
release:
runs-on: ubuntu-latest
steps:
# build, sign, publish stepsTag a release with
git tag v1.2.3 && git push --tagsCatch regressions from dependency updates even when you haven't pushed:
on:
schedule:
- cron: '0 6 * * 1' # Every Monday at 6am
workflow_dispatch: # Manual triggerThe GitHub Actions free tier gives 2,000 minutes/month — more than enough for a solo Android project. A typical build + test run takes 4–6 minutes, so you have capacity for 300+ runs per month before hitting limits.
The upfront setup cost is an hour. The ongoing benefit is every release decision backed by a green CI run rather than a mental checklist.
Sudarshan Chaudhari
AI Systems Builder / Product Engineer
Bangkok, Thailand
Solo Android developer with 13+ years in QA, building Android apps, AI automation systems, and developer tools at SudarshanTechLabs.
Related Posts
Related Apps
Real-time family location sharing — Firebase Realtime DB for sub-second propagation, WorkManager + ForegroundService for OS-compliant background collection, geofencing via Google Maps API.
Building something? Available for Android dev and QA consulting.
Work with meComments — powered by Giscus