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.
Continuous integration for Android doesn't require a DevOps team. Here's a lean CI/CD setup that runs tests automatically, builds release APKs/AABs, and deploys to Play Store — all from GitHub Actions.
On this page
Manual builds and releases are the enemy of consistency. One missed Gradle sync, one forgotten signing configuration, one skipped test run — and a broken build ships to production.
Here's how to automate the Android build pipeline without overengineering it.
All of this runs in GitHub Actions. No external CI service required.
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
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'
- name: Cache Gradle
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
- name: Run unit tests
run: ./gradlew test
- name: Run lint
run: ./gradlew lint
- name: Build debug APK
run: ./gradlew assembleDebug
- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results
path: '**/build/reports/tests/'This is the minimum viable pipeline. Tests + lint + build, triggered on every PR.
Never commit signing credentials. Store them in GitHub Secrets:
KEYSTORE_BASE64KEYSTORE_PASSWORDKEY_ALIASKEY_PASSWORDTo encode your keystore:
base64 -i release.jks | pbcopy # macOS — copies to clipboard# .github/workflows/release.yml
name: Release
on:
push:
tags:
- 'v*.*.*'
jobs:
release:
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'
- name: Cache Gradle
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ hashFiles('**/*.gradle*') }}
- name: Decode keystore
run: |
echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > app/release.jks
- name: Build release AAB
run: |
./gradlew bundleRelease \
-Pandroid.injected.signing.store.file=release.jks \
-Pandroid.injected.signing.store.password=${{ secrets.KEYSTORE_PASSWORD }} \
-Pandroid.injected.signing.key.alias=${{ secrets.KEY_ALIAS }} \
-Pandroid.injected.signing.key.password=${{ secrets.KEY_PASSWORD }}
- name: Upload to Play Store
uses: r0adkll/upload-google-play@v1
with:
serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }}
packageName: com.sudarshantechlabs.myapp
releaseFiles: app/build/outputs/bundle/release/*.aab
track: internal[!WARNING] Never pass signing credentials as command-line arguments in scripts you commit to the repo. Use GitHub Secrets and pass them via
flags or environment variables at CI runtime only.code-P
The
upload-google-playSERVICE_ACCOUNT_JSONAutomate version code from the git tag:
// build.gradle.kts
val versionPropsFile = rootProject.file("config/version.properties")
val versionProps = Properties().apply {
if (versionPropsFile.exists()) load(versionPropsFile.inputStream())
}
android {
defaultConfig {
versionCode = (versionProps["VERSION_CODE"] as String).toInt()
versionName = versionProps["VERSION_MAJOR"] as String + "." +
versionProps["VERSION_MINOR"] as String + "." +
versionProps["VERSION_PATCH"] as String
}
}When you push a tag
v2.1.3version-bump.shEnforce your pipeline at the repo level:
maintestNow nothing merges to main without passing CI.
| Trigger | Actions |
|---|---|
| PR opened/updated | Tests + lint + debug build |
| Merge to main | Full test suite + release build |
| Tag code | Sign AAB + upload to Play Store internal |
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
Real-time family location sharing — Firebase Realtime DB for sub-second propagation, WorkManager + ForegroundService for OS-compliant background collection, geofencing via Google Maps API.
ReadPrivate dream journal — structured entry capture, pattern tagging, and optional Claude-powered insight generation. All data stays on-device by default.
ReadWorkout tracker — exercise logging with set/rep/weight history, goal progression, and local Room DB persistence. No account, no cloud sync required.
Read