Since august I’ve got a new job. We are currently a team of three developers working on a client/server application. The server is developed in Rails. The client is developed on the Android platform.
A few months ago Google decided to use the excellent Gradle build system as its tool for building Android applications, instead of Ant + Ivy combo. I really see it as a huge improvement. I won’t discuss the pros and cons of Gradle here, there are plenty of resources on that topic on the web.

Gradle makes it easy to add custom tasks and plugins, and that’s what I want to discuss here.

You can find the code of this plugin on Github but keep in mind that it’s not working at the time of this writing.


We use FlightTest in order to upload beta builds of our app. Needless to say, we’d like to automate it.
So it’s a good opportunity to dust off my Groovy skills which I haven’t practiced a lot recently, I have to admit.

Note: of course I could use Java as language for developing this plugin, since I’m primarily a Java developer, but I feel more productive when writing code in Groovy.

First things first : the task

We’ll start by writing a task whose goal is to upload an APK file to TestFlight, which is, in the end, what we want to achieve.

TestFlight has an API for uploading APK’s. It’s very easy. The only thing to note is that the content-type of the request is multipart/form-data.

So it should quite simple to write a Gradle task that uploads the build to TestFlight. For this, I’ll use the excellent HttpBuilder.

Here’s the code:

class TestFlightUploadTask extends DefaultTask {

    static final String URL = ''
    static final String CONTENT_TYPE = 'multipart/form-data'

    @Input String apkFilename
    @Input String apiToken
    @Input String teamToken
    @Input String notes
    @Input List<String> distributionLists
    @Input Boolean notify = false
    @Input Boolean replace = false

    void upload() {
        def http = new HTTPBuilder(URL)

        http.request(Method.POST, CONTENT_TYPE) { req ->

            MultipartEntity entity = new MultipartEntity()
            entity.addPart('file', new FileBody(new File(apkFilename)))
            entity.addPart('api_token', new StringBody(apiToken))
            entity.addPart('team_token', new StringBody(teamToken))
            entity.addPart('notes', new StringBody(notes))
            entity.addPart('distribution_lists', new StringBody(distributionLists.join(',')))
            entity.addPart('notify', new StringBody(booleanToString(notify)))
            entity.addPart('replace', new StringBody(booleanToString(replace)))
            req.entity = entity

    def booleanToString(def property) {
        property ? 'True' : 'False'

It is to be noted that I haven’t handled the exceptions. It is on my to-do list.

You could use this task as-is, without encapsulating it in a plugin, just like this :

// rest of build.gradle
task uploadToTestFlight(
        type: TestFlightUploadTask,
        group: 'deploy',
        description: 'Upload Staging APK to TestFlight',
        dependsOn: assembleStaging) {
    apkFilename         = 'build/apk/my-android.apk'
    apiToken            = '**************'
    teamToken           = '**************'
    notes               = 'Just a release'
    distributionLists   = ['Developers']
    notify              = true

and it would work. Actually that’s what I did until now.

But some things could be improved. For example I’d like to be able to create a task for each build variant, for example uploadToTestFlightStaging. But in order to be able to dynamically generate the upload tasks I need to know when the build variants are generated, thus I need to extend the Android plugin.

I’ve looked at the Android plugin source code and must say it’s intimidating, I don’t understand all the ins and outs yet.

And that’s why I asked mentoring. I hope to be able to have a fully working - with error handling, tests, and deployment - within a week or two.

I hope it will be useful for many Android/TestFlight developers.

I’ll keep you informed on the status of development.


I’d like to thank Benjamin Muschko for its quick response and support.