DEV Community

Cover image for Three Continuous Integration options for Android Projects
Levi Albuquerque
Levi Albuquerque

Posted on

Three Continuous Integration options for Android Projects

Hello y'all :)

Since we last spoke I've been meddling with some pretty nice things. One of those is Continuous Integration options for Android projects. So I've identified three that can be used with various levels of setup difficulty, usage price and integration and I came up with a list of options that worked for me. Note that I had had experience with CI only in Django projects, besides I'm no expert in CI at all.

Quick reminder: Continuous Integration

Continuous integration is a great principle that helps projects evolve safely when considering a team of developers. This means that the devs can integrate the code they write continuously, this code is regularly "checked" for bugs (through tests written by the devs themselves) and helps detect bugs early whilst keeping everyone up to date. Usually CI practices involve setting up a CI Server responsible for checking the code that the devs write in order to detect bugs. You may be asking yourself how does it do that? Well, this involves a setup in the server as well as a mindset with the developers that use the tool:

  • We keep the code in a remote repository (Github, Gitlab, Bitbucket...). Well, everyone does that already...
  • The CI server is configured to "know" how to build and test the code. This is usually a script that mimics the commands we use locally to do those same things. For example, in Android when we want to run unit tests we usually do:./gradlew test. So our CI server "knows" how to run that command.
  • The CI server also knows about how to build and sometimes how to deploy the artifacts produced by the build (apks in the case of Android).
  • We usually commit code to the repo and the CI server, that knows about push events, will pull the code and run some pre-defined steps to run and build the code.
  • We should frequently push code (to keep the "continuous" part of the integration flowing)
  • We shouldn't push untested code, this means we should at least run the existing tests locally to see if our changes didn't break them. Ideally you need to add to the test suit whilst developing (TDD, right?).
  • We shouldn't push broken, half-finished or otherwise not built code.

Now with the options

Option 1 : Circle CI

The first option I'd like to talk about is Circle CI. It's very advertised due to its nice integration with Github. For free you get 1 container that will be able to run 1 "parallel" job. It's enough for testing and small projects so let's dive into it.

You'll see that with such tools the setup is quite similar, we need to add a configuration file to our project's base folder and all the CI setup is done in this file.

In the case of Circle CI this file is called ./circleci/config.yml. The structure is also somehow similar to other options, you can see the complete file here.
Basically, this files tells Circle CI the following:

1- Define a job step called "build"

    working_directory: ~/code
Enter fullscreen mode Exit fullscreen mode

2- Use the docker image circleci/android:api-28-alpha which contains all the Android SDK options for running command line builds/tests/deploys.

      - image: circleci/android:api-28-alpha
Enter fullscreen mode Exit fullscreen mode

3 - Define a series of steps that include making gradlew runnable, download the project dependencies, running linters, building and unit testing.

      - run:
         name: Chmod permissions
         command: sudo chmod +x ./gradlew
      - run:
          name: Download Dependencies
          command: ./gradlew androidDependencies
      - run:
          name: Run Unit Tests
          command: ./gradlew test
      - run:
          name: Run Linters
          command: ./gradlew check
      - run:
          name: Build
          command: ./gradlew assemble
Enter fullscreen mode Exit fullscreen mode

Please note, this configuration file is a simplified version. We can add many steps to this build which include integration and UI tests, packaging and even deployment. This is just a simple example.

Once you've added the configuration file all you need to do is go in the Circle CI page and add a new project pointing it to your Github repo. This part is really straightforward so here's a picture of the builds when they're executed in their servers:


Once you integrate Circle CI you can add it as a dependence in your PR workflow, that way the tests will need to pass in the CI server before you can merge. It will show in your PR like this:


The image above is a great segway for our next option.

Option 2 : Gitlab

The next option is Gitlab. You can either host your complete project in their site and use the CI system, or you can use their integration with Github and use only CI option. If you choose the latter (which is the one I've tested for Android) the setup involves giving authorization to Gitlab to clone your repo and it will add a webhook (just like Circle CI) to monitor push events in the original repo.To finish our set up we'll add a configuration file just like we did with Circle CI, but this time our file is called .gitlab-ci.yml. Here's the complete file.

This configuration file was inspired by the one in this post. You can see that it's a bit similar to the one I used for Circle CI. The idea is the same:
1 - Define two stages, build and test. Also, tell Gitlab to use the java docker image:

image: openjdk:8-jdk

  - build
  - test
Enter fullscreen mode Exit fullscreen mode

2 - The before_script is used to do configurations you'll run before the main purpose of each step. In the case of the build step we use the build.gradle file to identify the compile sdk version we'd need to use. We also define an environment variable called ANDROID_COMPILE_SDK that will be used to download the SDK files (this can be easily added in the CI configuration menu in Gitlab, under the option "variables"). The variable ANDROID_SDK_TOOLS is used to download other sdk files. In this script we also set up an environment variable called ANDROID_HOME pointing to the folder where we've installed the sdk tools. We add the folder to the container PATH so we can run its utilities in other places. Finally we made gradlew runnable.

    - export ANDROID_COMPILE_SDK=`egrep '^[[:blank:]]+compileSdkVersion'  app/build.gradle | awk '{print $2}'`
    - wget --quiet --output-document=/tmp/${ANDROID_SDK_TOOLS}.zip
    - unzip /tmp/ -d .android
    - export ANDROID_HOME=$PWD/.android
    - export PATH=$PATH:$PWD/.android/platform-tools/
    - echo y | .android/tools/bin/sdkmanager "platforms;android-${ANDROID_COMPILE_SDK}"
    - chmod +x ./gradlew
Enter fullscreen mode Exit fullscreen mode

3 - For any branch, except for master, I'll call the command ./gradlew assembleDebug. If the push event happened in the master branch we call ./gradlew assembleRelease.

    - ./gradlew assembleDebug

    - master
    - ./gradlew assembleRelease
Enter fullscreen mode Exit fullscreen mode

4 - For testing, if the push event happened in any branch, we'll call ./gradlew test.

  stage: test

    - export ANDROID_HOME=$PWD/.android
    - export PATH=$PATH:$PWD/.android/platform-tools/

    - ./gradlew test
Enter fullscreen mode Exit fullscreen mode

Note that because we've set the artifacts of the build phase to expire only in 4 hours, our before_script for the testing phase didn't need to setup the SDK all over again.

Option 3 : Jenkins

Circle CI and Gitlab are the "easy" options, but that comes with a cost. For full on professional projects the free resources might not be enough and then we'd need to go into one of the paid plans. For that reason I brought the third option: Jenkins.

Jenkins is open sourced and the limit for its operation is set by the server where you're hosting it. Awn, yeah. It's "free" because you won't pay a licence to use it, but you need to host it somewhere, and THAT's not free. I've been using a t2-small EC2 instance and it's been working great.

Most of the tutorials I've find about this are a bit outdated, but the basic steps are listed below:

1 - Install the JDK in your instance, this is usually a simple command such as the one bellow. Moreover, install unzip because you'll need it later and git for using git commands in the CI steps.

sudo apt-get install java-8-openjdk
sudo apt-get install unzip
sudo apt-get install git
Enter fullscreen mode Exit fullscreen mode

2 - Install Jenkins.
3 - Download the command line tools for Android, you can check which one is the latest here:

sudo -iu jenkins wget
Enter fullscreen mode Exit fullscreen mode

4 - Create a folder to host the android sdk and unzip the sdk into it:

sudo -iu jenkins mkdir sdk
sudo -iu jenkins unzip -d sdk
Enter fullscreen mode Exit fullscreen mode

5 - Accept the license:

yes |sudo -iu jenkins sdk/tools/bin/sdkmanager --licenses
Enter fullscreen mode Exit fullscreen mode

6 - Install Platform Tools:

sudo -iu jenkins sdk/tools/bin/sdkmanager 'platforms;android-27'
Enter fullscreen mode Exit fullscreen mode

Once you've gone through the steps you can start setting up jobs in your Jenkins dashboard. First we need to tell Jenkins where we've installed the SDK, this can be done by going into System Configuration in the Jenkins dashboard:


After setting the sdk folder we need to create a set of credentials so Jenkins can pull the code from our repository. You can go into the Credentials menu and under the Jenkins namespace click in Add Credential:

For username you can use your own Github username, the password is a bit different. You'll need to go into your Github account, under settings -> Developer settings options and select Personal token. You'll need to generate a token to be used by jenkins as the password for the credential.

After setting up the credential you can create the job. Go into new item in your Jenkins dashboard and select Freestyle project. In the following screen make sure you set up:

1- Github project


2 - Git repo, credentials and branches to watch for push events.


3 - Build trigger

Build triggers determine how our jobs will start running, for our case we want to detect push events from Github, so we select "GitHub hook trigger for GITScm polling".


For that to work you need to go into the settings for your repo and add a new webhook. Just fill in the payload URL option with: http://<JENKINS_HOST_ADDRESS>/github-webhook/.


4 - Build steps

Here we add two steps (by clicking in Add Gradle Script). Check to use gradlew wrapper and add the tasks assembleRelease and test. Don't forget to check the box to make gradlew runnable.


Woof. That's a lot to configure isn't it? I guess that's why they've come up with Blue Ocean... After that your jenkins setup is ready to receive push events and build/test automatically.

So, to sum it up:

Circle CI is great for its integration with Github, from what I've seen in the docs it is pretty versatile with a wide range of options to cover most CI scenarios out there. Gitlab is also very good, but there's somehow an overhead if you want to integrate it with Github, it feels less natural than Circle CI, but still very functional. I believe if you host your code with them, then it works really well (I've been working in projects where the code is hosted in Gitlab and we use the CI option almost seamlessly). Jenkins is perfect for customization, it offers the greatest flexibility, but there's a learning curve to understand how it works and the interface is less intuitive than the other two.

I guess that's it :) These are only my points of view from a small amount of time dedicated to exploring these tools. From those three I've only been working regularly with Gitlab so I can vouch for it, the other two, well, you've read my opinions... Also, these are NOT the only options out there, other famous option is Travis CI for instance.

Top comments (0)