Building a Kotlin Android app with Go logic

So my program Hashtree has been working on Linux/FreeBSD/Windows for some time now and it is stable. I use it everyday to sync the source code between multiple dev machines. However I wanted to use it on my phone to store and access important personal and business documents without using an untrusted third party cloud.

I have now successfully ported my CLI app to android by creating an Android front end that calls the various Go functions from inside an SDK .aar file. This file is generated by the gomobile tool.

Creating an Kotlin App with a Go Library

I recommend you clone my github repository as it contains all the files configured; then modify as necessary. The following files need to be configured:

settings.gradle

include ':app',  'gomobile'

This tells gradle to include the gomobile package in the build.

gomobile/build.gradle

configurations.maybeCreate("default")
artifacts.add("default", file('gomobile.aar'))

This tells gradle the name of the aar package.

gomobile/gomobile.iml
See here for the code.

app/build.gradle

dependencies {
 implementation fileTree(include: ['*.jar'], dir: 'libs')
 implementation project(':gomobile')
}

These config lines need to be added to tell gradle to include the gomobile library in the main ‘app’

Calling Go code from Kotlin

To call Go code from kotlin we just need to import the package:

import gomobile.Gomobile

Then call the internal function inside Kotlin:

Gomobile.testCall()

In this case our Go function doesn’t return anything but if it did you would just assign the val or var like normal:

val i = Gomobile.testCall()

The same goes if you wanted to pass in parameters:

val i = Gomobile.testCall("string")

Calling Kotlin Code from Go code

To call our Kotlin code we need to use callbacks. They sound scary at first but are quite straight forward.

We just need to create our callback in Go and then call it like a regular Go function:

package gomobile

import (
	"fmt"
	"time"
)

// callback
var jc JavaCallback

type JavaCallback interface {
	SendString(string)
}

// register our callback
func RegisterJavaCallback(c JavaCallback) {
	jc = c
}

func TestCall() {
	for i := 0; i < 100; i++ {
		time.Sleep(100 * time.Millisecond)
                // send a string to our Kotlin code
		jc.SendString(fmt.Sprintln("Counting... ", i))
	}
}

Then you need to create a Kotlin library (GoCallBack.kt):

package com.wordpress.clinetworking.android_kotlin_template

import gomobile.JavaCallback
import android.app.Activity
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.coroutines.experimental.CommonPool


class GoCallback(internal var context: Activity, internal var commoncontext: CommonPool) : JavaCallback {

    override final fun sendString(data: String) {
        try {
            var d: CharSequence
            if (!data.contains("\n")) {
                d = data + "\n"
            } else {
                d = data
            }
            val i = data.length
            println("TXT: $data : $i")

            context.runOnUiThread(java.lang.Runnable {
                context.textView.append(d)
            })

        } catch (e: Exception) {
            print("Error updating UI: ")
            println(e)
        }
    }
}

After this we need to register our callback in our MainActivity.kt:

// callback
        val gocb = GoCallback(this, CommonPool)
        Gomobile.registerJavaCallback(gocb)

The Finer Details

Now I snuck some more advanced things in the code above. The above code is from my example code I linked in the beginning. Let’s go through it one piece at a time.

Beginning in GoCallBack.kt:

class GoCallback(internal var context: Activity, internal var commoncontext: CommonPool) : JavaCallback {

This line makes sure we pass the Activity context and the CommonPool context. The Activity context is our UI thread. The CommonPool thread (may or may not be a real thread) is a Kotlin coroutine thread.

As we will be running our go code concurrently with our user interface we need access to both. We can not run our Go code in our UI thread as it is blocking code – reads files, connects to the Internet etc.

If we ran our Go code in the UI thread it would freeze until the Go function had returned!

context.runOnUiThread(java.lang.Runnable {
                context.textView.append(d)
            })

This snippet of wonderful code is the most wonderful and valueable of all! It took me forever to figure out and glues everything together.

As this Callback will be called on the CommonPool thread (we run the Go code there) it won’t have acces to the UI. This bit of code allows us to update the UI by appending to the textView with data from the CommonPool thread. Very cool stuff.

Now the last snipped it the Kotlin coroutine:

 button.setOnClickListener {
            launch(UI) {
                toast("Launching background process!")
                val i = withContext(CommonPool) { Gomobile.testCall() }
            }
        }

The launch(UI) tells us to launch of the UI thread. This is needed to pop up the little toast message. All other code unless specified will run on the UI thread.

the val i = withContext(CommonPool) tells Kotlin to run this code on the CommmonPool thread as discussed above.

The button.setOnClickListener is our code to respond to the user clicking the button. As the code in the launch(UI) {} block is all non blocking the button will be available again to be pressed. This means we could press it as many times as we want. Potentially causing issues.

In my program hashtree-mobile I specify the button to be disabled until the Go code returns.

So whats the big deal?

Well the big deal is this – we just created a multithreaded program that runs concurrent UI processes and business logic at the same time. The business logic (Go code) runs on the processor not in the JVM so has performance benifits.

With this simple code it is very easy to port CLI apps to Android and have them run fast, with next to no modification.

I only changed fmt.Println code in my hashtree-mobile app. This means a smaller code base and less time wasted rewriting code.

While it was a struggle to figure it out as new comer to Kotlin, Java and Android – the Jetbrains tools and programming language are exceptional and make programming fun again.

Be sure to clone my github repository and build it for yourself!

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s