CI/CD is a DevOps practice that allows development teams to push changes that get automatically tested and sent out for delivery and deployment. According to GitLab, a good CI/CD pipeline can maximize development time by improving an organization’s productivity, increasing efficiency, and streamlining workflows through built-in automation, testing, and collaboration.
Modern software development teams have became increasingly reliant on CI/CD practices — or continuous integration and continuous delivery, to be more precise — as they save developers and testers alike precious time by automating much of the manual labor that is needed to get the code from a development state and into production.
With the increased interest and dependence of software companies on CI/CD tools, many new options have been appearing that offer similar products aimed at facilitating and automating much of the development process for programmers. In this article, I will go over one such tool, GitHub Actions, and its application into modern Android development.
Jump ahead:
GitHub Actions is GitHub’s answer to the increasing demand for good and simple CI/CD tools. Because of the virtual proximity of the GitHub Actions pipeline to your code, this tool also allows for them to go beyond just DevOps and lets you run workflows when other events happen in your repository. For example, you can run a workflow to automatically add the appropriate labels whenever someone creates a new issue in your repository.
Instead of diving into GitHub Actions’ broad functionality, we’ll instead do a quick comparison with some of its competition, and follow that by showing how to get it set up for Android. For more information about what GitHub Actions are and what they do, check out what my fellow LogRocketeer had to say about the topic in this article, or go straight to the source and read the GitHub Actions official documentation.
Before continuing our exploration into why GitHub Actions is a better option than other CI/CD tools, let’s first make a couple of disclaimers. First, for the purpose of this exploration, I have briefly researched alternative tools like Bitrise, CircleCI, and TeamCity by JetBrains. Second, while the pricing of these tools varies greatly for many different factors, the considerations that we have taken will be from the perspective of an independent developer with a codebase hosted as a public GitHub repository.
There are two main reasons why GitHub Actions is a better option over more traditional tools like Bitrise, CircleCI, and TeamCity.
Price – We’ll start with the most common concern for teams of all sizes. While most competitors do offer free version of their software and only charge clients after a certain threshold, GitHub Actions goes one step further to facilitate this.
GitHub Actions is free for all standard GitHub-hosted runners in public repositories, and for self-hosted runners, according to it’s official documentation. For private repositories, each GitHub account receives a certain amount of free minutes and storage for use with GitHub-hosted runners.
Seamlessness – Did I mention that GitHub Actions lives one server down from your code repositories? The biggest advantage that GitHub Actions has over its competition is the fact that this tool is already integrated with your GitHub project and account(s). While traditional CI/CD tools require you to link your same GitHub account to their servers, GitHub Actions comes almost included with your repository already, and it is readily available with the click of a button.
Using GitHub Actions is not always perfect. Let’s go over the biggest disadvantage that using GitHub Actions may bring upon in contrast to its competition.
Youth – Differently to all of the tools that we’re comparing it to, GitHub Actions is by far the youngest CI/CD tool that we’ve mentioned today. This is a very clear disadvantage due to the fact that older platforms like TeamCity and CircleCI would have not only gone through some similar problems as GitHub Actions may have, but they’ve had more time to harden their tech, as well as to create more integrations and support for their users.
Now that we’re all convinced that GitHub Actions may be the most advantageous choice for a CI/CD tool, let’s grab a ride back with the Droid, and get our pipeline integration started through a simple Actions script.
GitHub Actions uses YAML script files for each workflow, where a workflow is a configurable automated process that will run one or more jobs, according to the GitHub docs, and these may run sequentially or in parallel. Workflows are generally triggered by events, such as a pull request being created, or a new tag being pushed, but workflows may also be triggered manually at any time.
Workflow scripts live in the .github/workflows/
location from the root directory of your repo. Where a push.yaml
file’s full path would be .github/workflows/push.yaml
. The following snippet of the top part of push.yml
will show the basic setup of a simple workflow that runs after every push to master
or develop
:
name: Push on: push: branches: [ "develop", "master" ] workflow_dispatch: jobs: build: name: Build runs-on: ubuntu-latest steps: - run: echo "The job was automatically triggered by a ${{ github.event_name }} event."
Let’s go over each of these fields before we get into the steps
section:
name
: The name of the workflow as it will be displayed in the GitHub Actions page
on
: This is where you specify the kind of events that may trigger this workflow. In this case, it will be triggered for every push
coming into either master
or develop
workflow_dispatch
: This flag lets the system know that you want to trigger this workflow manually from the interface
jobs
: Workflows may have one or more jobs, which run in parallel by default but may be run sequentially instead. In this example, we only have one job
called build
with a name
parameter of value Build
runs-on
: Indicates the kind of machine this job is going to run inside of
steps
: Each job has a series of steps
that may contain either a shell command or a public action from the GitHub marketplace to be run. In this example, the single step shown is a simple echo
command with a message containing the name of the event that triggered this workflow
There are many different steps
that can be taken for any given GitHub Action. For the purpose of this article, we’ll go over some of the most conventional steps used for the Android development ecosystem.
The next example will show the bottom part of the push.yml
file with all the steps needed to build the project after every push event:
jobs: build: name: Build runs-on: ubuntu-latest steps: - run: echo "The job was automatically triggered by a ${{ github.event_name }} event." - run: echo "This job is running on a ${{ runner.os }} server hosted by GitHub!" - uses: actions/checkout@v3 - run: echo "The ${{ github.repository }} repository has been cloned." - run: echo "Setting up JDK" - name: set up JDK 11 uses: actions/setup-java@v3 with: java-version: '11' distribution: 'temurin' cache: gradle - run: echo "The workflow is now ready to test your code." - name: Grant execute permission for gradlew run: chmod +x gradlew - run: echo "Building Debug APK." - name: Build with Gradle run: ./gradlew build - run: echo "Build status report=${{ job.status }}."
There’s quite a lot of noise through echo
commands on this example, but let’s go over all functions of type run
that don’t print to the screen, as well as uses
commands.
The first such example is our third step, uses: actions/checkout#v3
. This action is simply checking out the repository into the machine where this job is running.
Followed by two more prints commands, the next action has a particular name
field that explains what the uses: actions/setup-java@v3
action is doing. This same action is followed by some metadata inside the with
tag, which is specifying the Java version and the build tool being used; in this case, it is Gradle.
We have a few more messages printed as well as some permissions refactor, but on one of the last lines, we find our last command run: .gradlew build
, which simply builds the debug version of project. This command is followed by a print reporting the status of the build.
Before we move on to more advanced scripts, let’s take a step back and look at the topic of secrets. GitHub secrets is a safe place to hide confidential data, like signing or API keys. GitHub Actions may then take these secrets and use them as part of its actions and commands.
For more information about how to create GitHub secrets, their relationship to the Android ecosystem, and how to use them within GitHub Actions, check out this article.
For our next example, we’ll go over a job
that builds the release
build of our project, and signs it using a keystore found inside GitHub secrets:
jobs: build: name: Generate App Bundle runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: set up JDK 11 uses: actions/setup-java@v3 with: java-version: '11' distribution: 'temurin' cache: gradle - name: run: chmod +x gradlew - name: Bundle 'release' with Gradle run: ./gradlew bundleRelease - name: Sign AAB id: sign_aab uses: r0adkll/sign-android-release@v1 with: releaseDirectory: app/build/outputs/bundle/release signingKeyBase64: ${{ secrets.SIGNING_KEYSTORE }} alias: ${{ secrets.SIGNING_ALIAS }} keyStorePassword: ${{ secrets.SIGNING_KEYSTORE_PASSWORD }} keyPassword: ${{ secrets.SIGNING_ALIAS_PASSWORD }} - run: echo "Build status report=${{ job.status }}." - name: Upload App Bundle uses: actions/upload-artifact@v1 with: name: aab path: ${{steps.sign_aab.outputs.signedReleaseFile}}
Skipping right into the new commands only from the example above, we first find run: ./gradlew bundleRelease
, which, differently to the build command from our first example, triggers a build with the release
build variant, and makes an App Bundle .aab
file instead of the regular .apk
file. To generate an APK instead, a simple call of ./gradlew buildRelease
will get the job done.
The next new command we’ve got is uses: r0adkill/sign-android-release@v1
. This action was create by the community. It simply takes the freshly generated build file, in this case in the form of an AAB, and signs it using the certificate and variables found inside the nested with:
container. As we can see, we’re using our GitHub secrets as part of the command mentioned before.
The last new command we have in this second example is uses: actions/upload-artifact@v1
. This action simply takes a variable under the given path
, and saves it using the given name
for later use within the same workflow. More on that on the next and final example.
For our last example, we’ll go over one last job that uses the job from above to get a resulting AAB, and upload that into a GitHub Release for the given repository:
jobs: release: name: Release App Bundle needs: build runs-on: ubuntu-latest steps: - name: Download AAB from build uses: actions/download-artifact@v1 with: name: aab - name: Create Release id: create_release uses: actions/create-release@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: tag_name: Tag Name release_name: Release Name - name: Upload Release AAB id: upload_release_asset uses: actions/[email protected] env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ steps.create_release.outputs.upload_url }} asset_path: aab/app-release.aab asset_name: ${{ github.event.repository.name }}.aab asset_content_type: application/zip - run: echo "Upload returned with status=${{ job.status }}."
There are three main functions here, with a final print indicating the status of final upload job. Before we look at each command one by one, first we should address the new field we added into this workflow. The field needs
presupposes that there’s another job with the build
name that this job will execute, and potentially utilize any assets the preceding job might upload.
Returning to the commands, we start with uses: actions/download-artifact@v1
, which downloads an asset of name aab
, the same asset our last example uploaded. This means that this first command assumes that a job like the one from our last example is ran beforehand in order to find the required artifacts.
Moving on to the next action, uses: actions/download-artifact@v1
attempts to create a new GitHub release for the given repository, using the GitHub token found in the secrets to identify the environment. Additionally, this function takes both a tag_name
and release_name
in which we’ve added placeholders in order to keep the example a bit simpler.
The last function that we haven’t seen before in this last example is uses: actions/[email protected]
. This action is taking the .aab
file inside our asset through the asset_path
, defined by the asset_content_type
variable and having the name from asset_name
. This asset is then attempted to upload into the upload_url
path. The action emits a success result status at the end, which we print out in the next and last echo
command right after.
Evidently, there are a lot more acts that can be done as part of the CI/CD pipeline using GitHub Actions. These include steps as fundamental as testing your code, to automating complex flows that span multiple jobs and chain multiple workflows.
My hope is that this guide serves as a starting point for the readers to quickly get GitHub Actions up and running, and that they’re able to expand on this basis to cover their particular CI/CD needs. With the help of the GitHub community, finding new and useful actions to everyone’s respective needs shouldn’t be a tall order, and we’ve all got to start somewhere!
LogRocket is an Android monitoring solution that helps you reproduce issues instantly, prioritize bugs, and understand performance in your Android apps.
LogRocket also helps you increase conversion rates and product usage by showing you exactly how users are interacting with your app. LogRocket's product analytics features surface the reasons why users don't complete a particular flow or don't adopt a new feature.
Start proactively monitoring your Android apps — try LogRocket for free.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.
Handle frontend data discrepancies with eventual consistency using WebSockets, Docker Compose, and practical code examples.
Efficient initializing is crucial to smooth-running websites. One way to optimize that process is through lazy initialization in Rust 1.80.
One Reply to "Android CI/CD using GitHub Actions"
The Github action r0adkill/sign-android-release@v1 seems to have issues with using an old js version and looks like it was abandoned by the developer.
An alternative is ilharp/sign-android-release@v1.