Often an open source project will have API keys, auth tokens, and other secrets that definitely shouldn’t end up in source code. In my current open source project (a rewrite of APOD Wallpaper) I needed to store my APOD API key.

In the past, I’ve just added it to my local.properties and removed the file from git, but this approach doesn’t work when using GitHub Actions!

This post is available as a GitHub gist.

Note: As per a couple of comments, you could skip the properties file and use System.getEnv(x). I personally prefer keeping it in the project’s gradle file, to keep the secret only accessible from the project itself.

Approach to secrets

The approach to storing the secret isn’t going to change too much, there’ll just be a few changes to cater for GitHub Actions (italic = new):

  1. Store the secret in our local properties
  2. Store the secret in GitHub’s “secret” functionality
  3. In the CI, load this secret into a new local.properties file.
  4. In Gradle, manually load the local.properties file
  5. Load the secret into the BuildConfig.
  6. At runtime, access this new field.

1. Setting up local secret

All we need to do is open the autogenerated local.properties, and add our secret into it. Make sure to include the speech marks!


2. Adding secret to GitHub

Next, we need to go to the repository’s “Settings”, then “Secrets”, then “New secret”. This will let us add a secret with a name & value. Don’t include speech marks this time!

3. Accessing secret in GitHub Actions

There’s already plenty of great guides to getting GitHub Actions working with Android (simple, more complex, even more complex). For this project, here’s the full build.yml.

The only addition we need to make is a new job step in build.yml to put the secret into a new local.properties file. This must be done after the code is checked out, or the file will be put in the wrong location:

- name: Access APOD_API_KEY
  run: echo APOD_API_KEY=\"$APOD_API_KEY\" > ./local.properties

There’s 4 steps to this process:

  1. Access the secret itself ($)
  2. Use it to set an environment variable (env: APOD_API_KEY)
  3. Write the Groovy code ourselves to set a variable we can access in Gradle (run: echo x = \"y\")
  4. Save this code into a new local.properties file: ( > ./local.properties)

4. Loading the local properties file in Gradle

In our app-level build.gradle, we need to load our properties file manually, then access the key within. This isn’t necessary when running locally, but will be needed for the CI!

We do this by adding a new function, outside of android {} or dependencies {} etc:

String getApiKey() {
    def propFile = rootProject.file("./local.properties")
    def properties = new Properties()
    properties.load(new FileInputStream(propFile))
    return properties['APOD_API_KEY']

5. Loading the secret into BuildConfig

Next, still in the same file, we need to add our secret into the BuildConfig. I’m using the default config, but it’s pretty straightforward to have different values for different flavours / build types:

android {
    defaultConfig {
        buildConfigField ("String", "API_KEY", getApiKey())

6. Accessing the value at runtime

Finally, the simplest step of all! Once we’ve done a build to populate the BuildConfig, we can just access our secret with BuildConfig.API_KEY. Now, when GitHub actions runs our app can access the secret, and the build passes.


Whilst trying to find a way to store my secret, I was quite surprised there didn’t seem to be any complete guides around. The closest I could find was an unvoted StackOverflow post that had most of the information, and still required figuring a few things out!

Currently this post’s method only covers the case where there’s 1 secret to store. If multiple are needed, a quick solution would be just manually adding more to each step, but I’m sure it’s not ideal.

GitHub actions is still (somewhat) new, but so far it definitely seems a strong contender to traditional CIs. The “actions” store seems an excellent way to share functionality between projects, and encourage a strong CI community. Having simple tasks like checking out code and setting up the JAVA JDK as actions makes sure everyone is comfortable with the process, whilst keeping the build config readable.