Setting up CI/CD for an Android application via Gradle

NOVASOLUTIONS.TECHNOLOGY is engaged in the development, support and maintenance of iOS, Android, PWA mobile applications. We have extensive experience and expertise in publishing mobile applications in popular markets like Google Play, App Store, Amazon, AppGallery and others.
Development and support of all types of mobile applications:
Information and entertainment mobile applications
News apps, games, reference guides, online catalogs, weather apps, fitness and health apps, travel apps, educational apps, social networks and messengers, quizzes, blogs and podcasts, forums, aggregators
E-commerce mobile applications
Online stores, B2B apps, marketplaces, online exchanges, cashback services, exchanges, dropshipping platforms, loyalty programs, food and goods delivery, payment systems.
Business process management mobile applications
CRM systems, ERP systems, project management, sales team tools, financial management, production management, logistics and delivery management, HR management, data monitoring systems
Electronic services mobile applications
Classified ads platforms, online schools, online cinemas, electronic service platforms, cashback platforms, video hosting, thematic portals, online booking and scheduling platforms, online trading platforms

These are just some of the types of mobile applications we work with, and each of them may have its own specific features and functionality, tailored to the specific needs and goals of the client.

Showing 1 of 1 servicesAll 1735 services
Setting up CI/CD for an Android application via Gradle
Medium
~2-3 business days
FAQ
Our competencies:
Development stages
Latest works
  • image_mobile-applications_feedme_467_0.webp
    Development of a mobile application for FEEDME
    756
  • image_mobile-applications_xoomer_471_0.webp
    Development of a mobile application for XOOMER
    624
  • image_mobile-applications_rhl_428_0.webp
    Development of a mobile application for RHL
    1052
  • image_mobile-applications_zippy_411_0.webp
    Development of a mobile application for ZIPPY
    947
  • image_mobile-applications_affhome_429_0.webp
    Development of a mobile application for Affhome
    862
  • image_mobile-applications_flavors_409_0.webp
    Development of a mobile application for the FLAVORS company
    445

CI/CD for Android Applications via Gradle

Gradle is Android's build system, and most CI/CD for Android projects revolves around key Gradle commands. But running ./gradlew assembleRelease and properly configuring CI/CD are different tasks. APK/AAB signing, versioning, build variants management, dependency caching—all require explicit configuration.

Signing in CI Without Keystore in Repository

Keystore in Git is critical error, even in private repository. Correct scheme: keystore Base64-encoded → CI secret → decode on-the-fly.

# GitHub Actions
- name: Decode Keystore
  run: |
    echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 --decode > app/release.keystore

- name: Build Release AAB
  run: ./gradlew bundleRelease
  env:
    SIGNING_STORE_FILE: release.keystore
    SIGNING_STORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
    SIGNING_KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
    SIGNING_KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}

In app/build.gradle.kts:

signingConfigs {
    create("release") {
        storeFile = file(System.getenv("SIGNING_STORE_FILE") ?: "debug.keystore")
        storePassword = System.getenv("SIGNING_STORE_PASSWORD") ?: ""
        keyAlias = System.getenv("SIGNING_KEY_ALIAS") ?: ""
        keyPassword = System.getenv("SIGNING_KEY_PASSWORD") ?: ""
    }
}

buildTypes {
    release {
        signingConfig = signingConfigs.getByName("release")
        isMinifyEnabled = true
        proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
    }
}

Keystore doesn't end up in artifacts—delete after build: rm -f app/release.keystore.

Versioning via CI

Android versionCode must be unique per build. Mistake—use 1, 2, 3 manually or forget to update. On CI—automatically:

// build.gradle.kts
val ciPipelineNumber = System.getenv("CI_BUILD_NUMBER")?.toIntOrNull() ?: 1
val gitCommitCount = "git rev-list --count HEAD".runCommand().trim().toIntOrNull() ?: 1

android {
    defaultConfig {
        versionCode = ciPipelineNumber.takeIf { it > 1 } ?: gitCommitCount
        versionName = "2.4.${gitCommitCount}"
    }
}

gitCommitCount as versionCode—reliable: monotonically increasing, independent of CI system.

Gradle Performance: Key Levers

Slow Gradle—common complaint. Several concrete settings in gradle.properties:

# Parallel module build
org.gradle.parallel=true
# Configure only needed modules
org.gradle.configureondemand=true
# Build cache
org.gradle.caching=true
# JVM heap
org.gradle.jvmargs=-Xmx4g -XX:+UseParallelGC
# Build config only for active build
android.defaults.buildfeatures.buildconfig=false

org.gradle.caching=true—local task cache. With unchanged module code, Gradle takes result from cache without recompilation. On CI this works through actions/cache (GitHub) with key by .gradle file hash.

Typical before/after with proper caching on ubuntu-latest runner:

  • Without cache: ./gradlew assembleRelease—8–12 minutes
  • With Gradle daemon + dependency cache: 2–4 minutes

Build Variants and CI

Different variants for different environments—standard:

flavorDimensions += "env"
productFlavors {
    create("dev") {
        applicationIdSuffix = ".dev"
        buildConfigField("String", "API_URL", "\"https://api.dev.example.com\"")
    }
    create("prod") {
        buildConfigField("String", "API_URL", "\"https://api.example.com\"")
    }
}

On CI—separate jobs per variant:

  • PR → assembleDevDebug + tests
  • merge to develop → assembleDevRelease + Firebase App Distribution
  • release tag → bundleProdRelease + Google Play

Google Play Upload via Gradle

gradle-play-publisher plugin:

// build.gradle.kts
plugins {
    id("com.github.triplet.play") version "3.9.1"
}

play {
    serviceAccountCredentials.set(file(System.getenv("PLAY_STORE_JSON") ?: "play-store-credentials.json"))
    track.set("internal")
    defaultToAppBundles.set(true)
}
./gradlew publishProdReleaseBundle

Credentials JSON—service account from Google Play Console with Release manager access. On CI—pass via environment variable or temporary file, like keystore.

Timeline

Gradle setup with signing, versioning, basic CI (GitHub Actions/GitLab CI): 2–4 days. Full configuration with build variants, Google Play deploy, Gradle performance optimization, test runners: 1–1.5 weeks. Cost calculated individually.