Publishing to Google Play using GitHub Actions
Tags: android, cicd, githubactions
Published: 2023-08-08 17:45:07

In this tutorial, we are going to create a GitHub Action workflow that creates the _Android App Bundle_ (AAB), signs it, and publishes it to Google Play.

This action will be triggered when a new release is created in the repository.

![GitHub Action Workflow that publish to Google Play](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gj9o3rvuu43o3uspacmz.png)

## Before Starting

If you are familiar with the general flow to publish an app you can skip this section.

Before starting to create our workflow, we need to review the process to sign and publish an AAB. 

1. Generate the AAB.
1. Sign the AAB with your upload key.
1. Upload the signed AAB to Google Play. The Play App Signing signs the AAB with the app key.
1. Publish the new app version.


In the early days of the Google Play Store, every developer self-signed their application, this had some inconveniences such as:

- If they lost their key, they won’t be able to publish updates anymore.
- The process to rotate a key that was compromised or misused was diffcult.

Nowadays with Play App Signing the process is easier. Google handles our app signing key. On the other hand, to upload our AAB we will need to sign it with our upload key. So, we will have two keys, the upload (we will need to store this one) key and the app key, thanks to Play App Signing both of them could be changed


![Play store app signing process](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a1y4bq8yax6rc9m736vi.png)
<figcaption>Play Store app signing process.</figcaption>

## Creating our GitHub Action

For this tutorial you will need to have:
- An upload key
- Play App Signing configured

If you don’t have any of this you could follow the [official Google docs](https://developer.android.com/studio/publish/app-signing)

The steps you have to follow to publish your app to Google Play using GitHub Actions are:

1. [Creating our signing configuration.](#creating-our-signing-configuration)
1. [Adding our secrets to GitHub.](#adding-our-secrets-to-github)
1. [Adding our GitHub Action Workflow.](#adding-our-github-action-workflow)


### Creating our signing configuration

You have probably seen examples of using another GitHub Action to sign the AAB, however, we can take advantage of the _Graddle Wrapper_ to build and sign our AAB. In order to do this we need to specify our signing configuration and the signing key details.


First in your module’s `build.gradle` (example: `app/build.gradle` ) you need to add the details for the keystore properties file

```
// app/build.gradle
...

// Create a variable called keystorePropertiesFile, and initialize it to your keystore.properties file, in the rootProject folder.
def keystorePropertiesFile = rootProject.file("keystore.properties")

// Initialize a new Properties() object called keystoreProperties.
def keystoreProperties = new Properties()

// Load your keystore.properties file into the keystoreProperties object.
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))

android {
    ...
}
```

Now, in the same file, we need to add the details for the signing configuration.


```
// app/build.gradle
...

android {
    ...

    signingConfigs {
        release {
            storeFile file(keystoreProperties['storeFile'])
            storePassword keystoreProperties['storePassword']
            keyAlias keystoreProperties['keyAlias']
            keyPassword keystoreProperties['keyPassword']
        }
    }
}
```

Finally, in your desired build type add the signing config

```
// app/build.gradle
...


android {
    ...

    buildTypes {
        release {
            ...
            signingConfig signingConfigs.release
        }
    }
```

### Adding our secrets to GitHub

#### Adding Keystore secrets

As you saw in the previous step, we are expecting a `keystore.properties` file with our key (upload key), since this is a sensitive value, we don’t want to put this file into our repository, so we are going to store it as a GitHub secret and when the GitHub Action run, we will create this file.

Since we can’t store files in the Actions secrets, first we need to encode it as base64, on Linux run the following command, and copy the output

`openssl base64 -in your_app.keystore`

Now go to your `repository/settings/secrets and variables/actions` and create a _new repository secret_. I’m naming it `SIGNING_KEY_PRODUCTION`, but of course, you can name it as you want.


![Add a new GitHub repository secret](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r9k4ltzcwdtfnr6a0xx2.png)
<figcaption>GitHub Actions secrets and variables page.</figcaption>

Now create the secrets for:
- key password
- key alias
- store password

At this point, our secrets should look like this:

![GitHub Actions secrets and variables](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/byk0nbkh2q9wp0e6s3j0.png)

<figcaption>GitHub Actions secrets and variables page with new secrets added.</figcaption>

#### Adding the service account secret

So far we would be able to create and sign our AAB in the GitHub Action that we will create, however, we are missing the part of publishing this AAB to the Play Store. In order to do this we will need a service account with permission to publish updates on our behalf.

To create your service account follow the [official Google docs](https://developers.google.com/android-publisher/getting_started)

Now that you have your service account JSON file, copy the whole content and create a new Actions secret.

At this point you should have something like this:

![GitHub secrets required to publish an AAB to Google Play](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h2m9g6790ux492zib8vh.png)
<figcaption>GitHub Actions secrets and variables page with all the required secrets.</figcaption>


### Adding our GitHub Action Workflow

This is our GitHub Action Workflow to publish to Play Store

```
name: Publish release AAB

on:
  push:
    tags:
      - v**

  # Allows you to run this workflow manually from the Actions tab
  workflow_dispatch:


jobs:
  publish_release_apk:
    runs-on: ubuntu-22.04

    steps:
      - name: Checkout repository
        uses: actions/checkout@v3

      - name: Set up our JDK environment
        uses: actions/setup-java@v3
        with:
          distribution: 'zulu'
          java-version: '11'

      - name: Create keystore file
        run: |
          echo ${{ secrets.SIGNING_KEY_PRODUCTION }} | base64 --decode > app.keystore

      - name: Create keystore file properties
        run: |
          echo "storeFile=../app.keystore" >> keystore.properties
          echo "keyPassword=${{ secrets.SIGNING_KEY_STORE_PASSWORD_PRODUCTION }}" >> keystore.properties
          echo "keyAlias=${{ secrets.SIGNING_KEY_ALIAS_PRODUCTION }}" >> keystore.properties
          echo "storePassword=${{ secrets.SIGNING_KEY_PASSWORD_PRODUCTION }}" >> keystore.properties

      - name: Generate release aab
        run: ./gradlew bundleRelease

      - name: Publish release aab
        uses: r0adkll/upload-google-play@v1
        with:
          releaseFiles: app/build/outputs/bundle/release/app-release.aab
          serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }}
          packageName: com.mywebsite.app
          track: production
          whatsNewDirectory: distribution/whatsnew
```

Let’s review the important parts

Here we specify that this action should be run when a new tag starting with `v`is created, for example `v1.0.0`

```
on:
  push:
    tags:
      - v**
```

Here we create the `app.keystore` file, decoding the content from our secret. After that, we create the `keystore.properties` file using the other secrets.

```
      - name: Create keystore file
        run: |
          echo ${{ secrets.SIGNING_KEY_PRODUCTION }} | base64 --decode > app.keystore

      - name: Create keystore file properties
        run: |
          echo "storeFile=../app.keystore" >> keystore.properties
          echo "keyPassword=${{ secrets.SIGNING_KEY_STORE_PASSWORD_PRODUCTION }}" >> keystore.properties
          echo "keyAlias=${{ secrets.SIGNING_KEY_ALIAS_PRODUCTION }}" >> keystore.properties
          echo "storePassword=${{ secrets.SIGNING_KEY_PASSWORD_PRODUCTION }}" >> keystore.properties

```

To generate our signed AAB we use `gradlew `and specify the build type that we want to use, in this case, release

```
      - name: Generate release aab
        run: ./gradlew bundleRelease
```

Our last step is to use the r0adkll [upload-google-play action](https://github.com/r0adkll/upload-google-play), we send the following parameters:

- releaseFiles: our signed AAB generated in the previous step
- serviceAccountJsonPlaninText: we use our secret when we have stored the Google service account with permissions to publish to Google Play
- packageName: our app package name
- track: the track to which you want to assign the uploaded app
- whatsNewDirectory: The directory of localized "what's new" files to upload as the release notes. The files contained in the whatsNewDirectory MUST use the pattern `whatsnew-<LOCALE>` where `LOCALE `is using the [BCP 47](https://tools.ietf.org/html/bcp47) format.
 An example of this is the following structure:

![Directory tree for whatsnew directory for android release notes](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8nx8dvqk28bnyrjogv9f.png)
<figcaption>Directory tree for whatsnew directory for android release notes.</figcaption>



```
      - name: Publish release aab
        uses: r0adkll/upload-google-play@v1
        with:
          releaseFiles: app/build/outputs/bundle/release/app-release.aab
          serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }}
          packageName: com.mywebsite.app
          track: production
          whatsNewDirectory: distribution/whatsnew
```

That is it, with this Workflow we can publish to Play Store every time we generate a new release from GitHub

## What is next?

### GitHub Action Workflow to run unit and instrumented tests

We could start working with this flow as it is, however, a very important component in any CI/CD pipeline is the confidence that we have in the process, one thing that will help us to gain confidence is testing, on Android it’s very clear the separation between the unit and instrumented test. 

Instrumented tests need the Android framework APIs, so they need a device (physical or emulated), for this reason, the recommendation is to use a macOS instance since having better support and performance.

Since the macOS instances are more expensive, we could have two GitHub Action Workflows, the first one will run the unit tests and the second one will run the instrumented tests. Both of them will run on PRs whose target is our main branch, and will also run when a merge to main occurs.

### GitHub Action Workflow to generate staging and production APKs

As part of a complete CI/CD pipeline, sometimes manual testing is needed, whether we have a QA team or just to simplify the process for developers, we could add a workflow to generate the APK. For example, creating the APK using our staging API when a PR is open, and generating the APK using the production API when a merge to main occurs, we will store the APK as a GitHub Action Artifact and anyone in the team could download it from there. In this way we could test our app easily before publishing a new release

```
// .github/workflows/create_staging_apk.yml

name: Create staging APK

on:
  pull_request:
    branches: [ main ]

  # Allows you to run this workflow manually from the Actions tab
  workflow_dispatch:


jobs:
  create_staging_apk:
    runs-on: ubuntu-22.04

    steps:
      - name: Checkout repository
        uses: actions/checkout@v3

      - name: Set up our JDK environment
        uses: actions/setup-java@v3
        with:
          distribution: 'zulu'
          java-version: '11'

      - name: Create keystore file
        run: |
          echo ${{ secrets.SIGNING_KEY_PRODUCTION }} | base64 --decode > app.keystore

      - name: Create keystore file properties
        run: |
          echo "storeFile=../app.keystore" >> keystore.properties
          echo "keyPassword=${{ secrets.SIGNING_KEY_STORE_PASSWORD_PRODUCTION }}" >> keystore.properties
          echo "keyAlias=${{ secrets.SIGNING_KEY_ALIAS_PRODUCTION }}" >> keystore.properties
          echo "storePassword=${{ secrets.SIGNING_KEY_PASSWORD_PRODUCTION }}" >> keystore.properties

      - name: Generate staging apk
        run: ./gradlew assembleStaging

      - uses: actions/upload-artifact@v3
        with:
          name: Signed staging apk
          path: app/build/outputs/apk/staging
          retention-days: 7

```