Git, Gradle, CI/CD and Play Store Release in Depth

Android Development with Kotlin: 21 Real Projects · Production Android · 5 min read · Updated

Build, test, ship

  1. You push a branch and open a pull request.
  2. CI builds the app and runs the tests on every push.
  3. A release job signs an AAB using secrets kept in CI, not in code.
  4. The build goes to internal testing, then a staged rollout.

A good developer ships reliably. This lesson covers the tools that make builds repeatable and releases safe: version control, Gradle, automated builds and the full Google Play process.

Git and GitHub

  • git init, add, commit, status, log, diff are the daily commands. Commit small, with messages that say why.
  • Use branches: git switch -c feature/cart. Merge or rebase back into main.
  • Open a pull request on GitHub so teammates can review, and let automated checks run on it.
  • Add a .gitignore for build/, .gradle/, local.properties and keystores. Never commit signing keys or API keys.
  • Resolve conflicts by reading both sides carefully, then run the tests.

Gradle with the Kotlin DSL

Gradle builds your project from build.gradle.kts files. A version catalog (gradle/libs.versions.toml) lists library versions in one place, so every module uses the same ones.

[versions]
room = "2.6.1"
retrofit = "2.11.0"

[libraries]
room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" }
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }

// in build.gradle.kts
dependencies {
    implementation(libs.room.runtime)
    implementation(libs.retrofit)
}

Build types, flavors and variants

A build type is debug or release. A product flavor is a version of the app, such as free and paid, or staging and production. Together they make build variants like stagingDebug. Use them for different base URLs, app names and Firebase projects. Put values in buildConfigField and read them from BuildConfig.

android {
    buildFeatures { buildConfig = true }
    flavorDimensions += "env"
    productFlavors {
        create("staging") {
            dimension = "env"
            applicationIdSuffix = ".staging"
            buildConfigField("String", "BASE_URL", "\"https://staging.example.com/\"")
        }
        create("production") {
            dimension = "env"
            buildConfigField("String", "BASE_URL", "\"https://api.example.com/\"")
        }
    }
}

Signing configurations

Read passwords from environment variables or an untracked keystore.properties file, never from the build file itself.

signingConfigs {
    create("release") {
        storeFile = file(System.getenv("KEYSTORE_PATH") ?: "release.jks")
        storePassword = System.getenv("KEYSTORE_PASSWORD")
        keyAlias = System.getenv("KEY_ALIAS")
        keyPassword = System.getenv("KEY_PASSWORD")
    }
}
buildTypes { release { signingConfig = signingConfigs.getByName("release"); isMinifyEnabled = true } }

Continuous integration

CI builds and tests every push automatically. With GitHub Actions, a file in .github/workflows describes the job. Store secrets (keystore as base64, passwords) in the repository's encrypted secrets.

name: Android CI
on: [push, pull_request]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with: { distribution: temurin, java-version: 17 }
      - uses: gradle/actions/setup-gradle@v4
      - run: ./gradlew testDebugUnitTest assembleDebug

Fastlane automates the release steps: building, signing, uploading to Play Console and publishing release notes. Use CD (continuous delivery) to push each tagged build to internal testing automatically.

Play Console in depth

  • App signing: use Play App Signing so Google keeps the app signing key. You upload with an upload key.
  • Versioning: versionCode must increase for each upload, versionName is shown to users, and the application ID is permanent.
  • Store listing and ASO: a clear title and short description with the words people search, a high-quality icon, 4 to 8 screenshots and a feature graphic. Honest descriptions help conversion and trust.
  • Policy forms: privacy policy URL, data safety, content rating, target audience, ads declaration and permissions declarations.
  • Testing tracks: internal (up to 100 testers, instant), closed (invited groups) and open (anyone). Personal developer accounts created recently may need to run a closed test with a minimum number of testers before production; check the current requirements.
  • Staged rollout: release to 5 or 10 percent, watch crashes and ratings in Android vitals, then increase or halt.
  • After release: reply to reviews, fix crashes first, and follow Play policy updates, since apps that break policy can be removed.

Your task

  1. Create a GitHub repository for one project with a proper .gitignore, then push a feature branch and open a pull request.
  2. Add staging and production flavors with different base URLs.
  3. Add the GitHub Actions workflow and see it pass.
  4. Write a release checklist for your app.

How to check it worked

  • No key or password is in the repository.
  • The CI run builds and tests every push.
  • You can explain internal, closed, open and production tracks.

Frequently asked questions

What are product flavors for?

They let one code base build different versions, such as staging and production, with their own app ID, name, base URL and settings.

Why use a version catalog?

It keeps all library versions in one file, so every module uses the same version and upgrades are one edit.

What is a staged rollout?

Releasing an update to a small percentage of users first. If crashes rise, you can stop before everyone gets it.