loading...

Signing Flutter Android apps for release in GitHub Actions

cddelta profile image CDDelta Updated on ・4 min read

There are various good tutorials on the web about how to run CI/CD workflows for Flutter on GitHub Actions, but none of them seem to touch on signing your Android apps for release with a Keystore that is required for publishing.

For security reasons, we do not want to check our Keystore file and passwords into source control. Doing so would reveal our private key to anyone with access to our repository which will allow them to impersonate our identity easier when uploading app releases to Google Play.

In this post, I'll walk you through the steps I had to perform to get Android release signing working for Flutter on GitHub Actions in a secure manner. I will not be covering how to set up CI/CD on GitHub Actions, so if you haven't already done it you can follow this great guide by Nabil Nalakath on how to do so.

You'll also want to make sure that you have followed the steps outlined in the Flutter docs on signing your app for release before getting started as we will be building on that.


1. Preparing our secrets

The first step we'll want to do is to add the required information for signing our app to GitHub. We can do so by going to the settings tab in the repo and selecting secrets from the side menu list.

If you followed the Flutter guide to sign your app for release, you'll have created a key.properties file in your project that looks like this:

storePassword=<password for keystore>
keyPassword=<password for keystore>
keyAlias=key
storeFile=<location of the key store file, such as /Users/<user name>/key.jks>

We'll add storePassword, keyPassword, and keyAlias properties as secrets to GitHub, formatting their names as KEY_STORE_PASSWORD, KEY_PASSWORD, and ALIAS respectively.

Then we'll provide GitHub with the actual Keystore to use when signing. We'll do this by encoding the Keystore to base64 and adding it as a secret under the name SIGNING_KEY.

To get the Keystore in base64 format, run this command:

openssl base64 -A -in <location of the key store file, such as /Users/<user name>/key.jks>

That's it for secrets configuration.

2. Updating gradle.build

Now that we have the required properties for our Keystore, we need to update our gradle.build file in /android/app to properly read in the data.

We'll do this by changing the following:

def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
    keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}

to

def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
    keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
} else {
    keystoreProperties.setProperty('storePassword', System.getenv('KEY_STORE_PASSWORD'));
    keystoreProperties.setProperty('keyPassword', System.getenv('KEY_PASSWORD'));
    keystoreProperties.setProperty('keyAlias', System.getenv('ALIAS'));
    keystoreProperties.setProperty('storeFile', System.getenv('KEY_PATH'));
}

which will look for the necessary properties as environment variables if key.properties is not found.

And that's it for gradle.build modifications.

3. Changing our workflow

Finally, we'll want to update our workflow to output the required Keystore to the CI environment and provide the necessary properties to flutter build.

Since we want to build a release version of our app, we'll also want to make sure we do not include the --debug flag for flutter build.

If you followed Nabil Nalakath's guide above you will end up with a workflow like the one I have below.

on:
  push:
    tags:
    - 'v*'
name: Test, Build and Release apk
jobs:
  build:
    name: Build APK
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v1
    - uses: actions/setup-java@v1
      with:
        java-version: '12.x'
    - uses: subosito/flutter-action@v1
      with:
        flutter-version: '1.7.8+hotfix.4'
    - run: flutter pub get
    - run: flutter test
    - run: flutter build apk --debug --split-per-abi
    - name: Create a Release APK
      uses: ncipollo/release-action@v1
      with:
        artifacts: "build/app/outputs/apk/debug/*.apk"
        token: ${{ secrets.TOKEN }}

Including the required modifications, we'll end up with:

on:
  push:
    tags:
    - 'v*'
name: Test, Build and Release apk
jobs:
  build:
    name: Build APK
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v1
    - uses: actions/setup-java@v1
      with:
        java-version: '12.x'
    - run: echo $SIGNING_KEY | base64 -d > android/app/key.jks
      env:
        SIGNING_KEY: ${{ secrets.SIGNING_KEY }}
    - uses: subosito/flutter-action@v1
      with:
        flutter-version: '1.7.8+hotfix.4'
    - run: flutter pub get
    - run: flutter test
    - run: flutter build apk --split-per-abi
      env:
        KEY_STORE_PASSWORD: ${{ secrets.KEY_STORE_PASSWORD }}
        KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
        ALIAS: ${{ secrets.ALIAS }}
        KEY_PATH: key.jks
    - name: Create a Release APK
      uses: ncipollo/release-action@v1
      with:
        artifacts: "build/app/outputs/apk/release/*.apk"
        token: ${{ secrets.TOKEN }}

This step outputs our Keystore to the CI environment for Gradle to use later:

- run: echo $SIGNING_KEY | base64 -d > android/app/key.jks
      env:
        SIGNING_KEY: ${{ secrets.SIGNING_KEY }}

And this step provides flutter build with the required properties for use in Gradle:

- run: flutter build apk --split-per-abi
      env:
        KEY_STORE_PASSWORD: ${{ secrets.KEY_STORE_PASSWORD }}
        KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
        ALIAS: ${{ secrets.ALIAS }}
        KEY_PATH: key.jks

And that's it! We can now commit our changes to GitHub.


Seeing the results

To see our release signing in action, we'll have to trigger a build in GitHub.

The process with which you do this will depend on your configured workflow but for the workflow above, we'll have to tag our commit with a tag like v0.9.0 with the commands:

git tag v0.9.0
git push origin v0.9.0

Once the build completes, it will show up in the releases section of your repository!


If you have any feedback regarding this post or questions, feel free to put them in the comments.

Discussion

pic
Editor guide
Collapse
bh0mbalziyad profile image
Ziyad Bhombal

Under this job I'd replace "build/app/outputs/apk/debug/*.apk" with "build/app/outputs/apk/release/*.apk" so that the job is able to fetch your APKs when the action completes and attaches it to the release. Otherwise you'll only get two archives with your source code in it'

- name: Create a Release APK
      uses: ncipollo/release-action@v1
      with:
        artifacts: "build/app/outputs/apk/debug/*.apk"
        token: ${{ secrets.TOKEN }}

But anyways, thanks for this post. I was able to get this up and running in an hour and manage to get my first ever rrelease out in a Github repository. It also helped me understand how to better leverage the use of Github secrets in further projects.

Collapse
cddelta profile image
CDDelta Author

Good catch, thanks for telling me about! I have updated the post.
I'm really glad to hear that my post has been able to help you with this!

Collapse
jacklee98332255 profile image
Info Comment marked as low quality/non-constructive by the community. View code of conduct
Jacklee

Flutter is Google’s SDK (Software Development Kit) for mobile app development. It is used to develop cross-platform compatible mobile and web apps.

Thank you for explaining the complicated App porting concept in simple steps. Also, while researching on app porting I have come across a similar site. Check it out:inoru.com/mobile-app-development