Continuous Deployment for Android Apps - noi-techpark/odh-docs GitHub Wiki

Preparation on the Google Play Store

We use the NOI Community App as an example throughout this tutorial, which has an App Name called NOI-Community App and a App ID it.bz.noi.community...

  • Go to https://play.google.com/console
  • Login with the developer account NOI Techpark with credentials from Passbolt
  • Create an app
    • The Application ID should be a reverse package name, for example: it.bz.noi.community
    • Do not put App, Android or another generic word into the App name or App ID field
  • Activate the internal testing track
  • Fill in additional needed information for that, such as Country, Testers etc.

Prepare secrets on GitHub

Create secrets under GitHub>Settings>Secrets (choose your repository):

  • GOOGLE_SERVICES_JSON
  • KEYSTORE_ALIAS
  • KEYSTORE_PASSWORD
  • KEYSTORE_PASSWORD_FOR_KEY
  • KEYSTORE_SIGNING_KEY
  • SERVICE_ACCOUNT_JSON

To get these credentials, please follow these instructions: https://www.raywenderlich.com/19407406-continuous-delivery-for-android-using-github-actions#toc-anchor-020

The exceptions to the documentation above is, that we create an .aab bundle and not an .apk anymore, since that is deprecated now. The mapping.txt file is also already included in the .aab bundle, therefore we omit this step.

On your repository create a GitHub Action file

  • Create folders mkdir -p .github/workflows
  • add a file main.yml
  • fill it with the following content:
### Continuous Integration
#
# This file is inspired by
# https://www.raywenderlich.com/19407406-continuous-delivery-for-android-using-github-actions

name: CI
on: [pull_request, push]
jobs:

  ## Build the app and run unit tests
  unit_tests:
    runs-on: ubuntu-20.04
    steps:
      - name: Checkout the code
        uses: actions/checkout@v2
      - name: Inject google-services.json
        env:
          GOOGLE_SERVICES_JSON: ${{ secrets.GOOGLE_SERVICES_JSON }}
        run: echo "$GOOGLE_SERVICES_JSON" > app/google-services.json
      - name: Test the app
        run: ./gradlew test

  ## Run tests on an Android Simulator
  ## We use macos as it has hardware acceleration for that
  android_tests:
    runs-on: macos-latest
    steps:
      - name: Checkout the code
        uses: actions/checkout@v2
      - name: Instrumentation Tests
        uses: reactivecircus/android-emulator-runner@v2
        with:
          api-level: 29
          script: ./gradlew connectedAndroidTest

  ## Build and sign the app, and upload it as Github Artifact
  build_and_deploy_to_play_store:
    needs: [ unit_tests, android_tests ]
    if: github.ref == 'refs/heads/development'
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v2
      - name: Inject google-services.json
        env:
          GOOGLE_SERVICES_JSON: ${{ secrets.GOOGLE_SERVICES_JSON }}
        run: echo "$GOOGLE_SERVICES_JSON" > app/google-services.json
      - name: Increment build number
        run: |
          set -eo pipefail
          BUILD_NUMBER=$(date "+%s")
          echo "VERSION_CODE=$BUILD_NUMBER" > app/version.properties
          cat app/version.properties
      - name: Generate Release AAB
        run: ./gradlew bundleRelease
      - name: Sign AAB
        uses: r0adkll/sign-android-release@v1
        # ID used to access action output
        id: sign_app
        with:
          releaseDirectory: app/build/outputs/bundle/release
          signingKeyBase64: ${{ secrets.KEYSTORE_SIGNING_KEY }}
          alias: ${{ secrets.KEYSTORE_ALIAS }}
          keyStorePassword: ${{ secrets.KEYSTORE_PASSWORD }}
          keyPassword: ${{ secrets.KEYSTORE_PASSWORD_FOR_KEY }}
      - name: Publish to Play Store closed alpha track
        uses: r0adkll/upload-google-play@v1
        with:
          serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }}
          packageName: it.bz.noi.community
          releaseFiles: ${{ steps.sign_app.outputs.signedReleaseFile }}
          track: alpha
          status: completed

The Increment build number step works only with the following addition to your build.gradle file under android {...}:

	// ...
	// Version Code - Start
	def Properties versionProps = new Properties()
	def versionPropsFile = file('version.properties')
	if (versionPropsFile.exists()) {
		versionProps.load(new FileInputStream(versionPropsFile))
	}
	def code = (versionProps['VERSION_CODE'] ?: "0").toInteger()
	// Version Code - End
	// ...
	defaultConfig {
		// ...
		versionCode code
		// ...
	}
	// ...