Android - Saving secrets with buildSrc

Tan Jun Rong avatar

Tan Jun Rong

Api keys and secrets

In almost all Android projects, we need to save a few API keys or secret client ids. The straight forward way would have been committing directly into the repo. While this makes the project the easiest to setup. It's is not a good practice, especially for an open source project, because we don't want the keys to be shared to the world.

The buildSrc folder

I've been using the gradle feature of buildSrc to extract logics and dependency versions out of my gradle file. It's easier to write since it's Kotlin and not Groovy. I know there is Kotlin DSL for gradle but it's harder to setup. buildSrc on the other hand only need less than 5 minutes to setup. Try it out if you haven't! Here's a good short tutorial from zeroturnaround.

In this article, I'll discuss about how to properly save your api keys and secrets using the buildSrc folder. So you'll need to setup buildSrc before proceeding, I'll grab my coffee wait for you as you setup. ☕🐱

saving Api keys and secrets in buildSrc

Alright, I assumed you've set it up! Nice! ☕
You should have something like this:

// $ tree buildSrc/
PROJECT_ROOT
├── build.gradle.kts
├── buildSrc.iml
└── src
    └── main
        └── java
             └── Something.kt
Requirement

Let's pretend that we're going to setup an Imgur client and Reddit client like this:

override fun onCreate(savedInstanceState: Bundle?) {
    // initializing the Imgur client & Reddit client
    val imgurClient = ImgurClient(BuildConfig.IMGUR_API_CLIENT_ID)
    val redditClient = RedditClient(BuildConfig.REDDIT_API_CLIENT_ID)
}

In your build.gradle, what you would previously do before extracting the secrets would look like this:

android {
    buildTypes {
        applicationVariants.all { variant ->
            variant.buildConfigField "String", "IMGUR_API_CLIENT_ID", "\"" + "abcdefgAPIKEY121313" + "\""
            variant.buildConfigField "String", "REDDIT_API_CLIENT_ID", "\"" + "abcdefgAPIKEY121313" + "\""
        }
    }
}

Let's extract it! 💪

1. First, create api_keys.properties in project root

The content of the file should look like this:

(project root)$ cat api_keys.properties
IMGUR_API_KEY=f387ac9c58c3ae9
REDDIT_API_KEY=WckNZW7vUxT3-g
2. Next, create a file Secrets.kt in buildSrc to read from api_keys.properties
buildSrc/
└── src
    └── main
        └── java
            ├── Secrets.kt
object Secrets {
    private val IMGUR_API_KEY = "IMGUR_API_KEY"
    private val REDDIT_API_KEY = "REDDIT_API_KEY"

    val imgurApiKey = apiKeysProperties().getProperty(IMGUR_API_KEY)
    val redditApiKey = apiKeysProperties().getProperty(REDDIT_API_KEY)

    private fun apiKeysProperties(): Properties {
        val filename = "api_keys.properties"
        val file = File(filename)
        if (!file.exists()) {
            throw Error("You need to prepare a file called $filename in the project root directory.\n" +
                    "and contain the IMGUR API key and REDDIT API key.\n" +
                    "the content of the file should look something like:\n\n" +
                    "(project root)$ cat $filename\n" +
                    "$IMGUR_API_KEY=abcde123253\n" +
                    "$REDDIT_API_KEY=abcde12323-g\n")
        }
        val properties = Properties()
        properties.load(FileInputStream(file))
        return properties
    }
}

quick explanation

  • apiKeysProperties() is used to read from the file api_keys.properties
  • some error handling if this file is not being found, this will help new members to realize they are missing a file while setting up the project
  • imgurApiKey and redditApiKey can be used in app/build.gradle
3. Using it in app/build.gradle:
android {
    buildTypes {
        applicationVariants.all { variant ->
            variant.buildConfigField "String", "IMGUR_API_CLIENT_ID", "\"" + Secrets.imgurApiKey + "\""
            variant.buildConfigField "String", "REDDIT_API_CLIENT_ID", "\"" + Secrets.redditApiKey + "\""
        }
    }
}
4. Using it inside your application:

Now we can finally use it exactly the same way as before in our MainActivity:

override fun onCreate(savedInstanceState: Bundle?) {
    // initializing the Imgur client & Reddit client
    val imgurClient = ImgurClient(BuildConfig.IMGUR_API_CLIENT_ID)
    val redditClient = RedditClient(BuildConfig.REDDIT_API_CLIENT_ID)
}

What changed is that the API keys are not in the codebase anymore!

5. add api_keys.properties into your .gitignore
// something_else...
api_keys.properties

Github sample

Here's a project I experimented with this setup in case you still couldn't get it working after reading this post: https://github.com/worker8/pixels/tree/cc03f99c1b340c0956fe3e0c9126d14e46bfc8bd

That's all!

Here's a random corgi picture before you go! 🐶

just chilling out at the beach
just chilling out at the beach

thanks for reading and see you next time! 👋

Tan Jun Rong avatar
Written By

Tan Jun Rong

Android Programmer who likes writing blogs, reading, coffee, snowboarding.
Published in Android
Enjoyed the post?

Clap to support the author, help others find it, and make your opinion count.