A Tutorial on writing Scala apps on Android

Language

By Maciej Gorywoda

Introduction

The Android platform runs on Android Runtime which is a virtual machine based on JVM and, although not identical, it’s very similar to it. As a consequence, it is possible to write Android apps in Scala, and in fact it’s possible to do it in more than one way. Here, in this document, we will focus on how to write a modern Android app with Scala that uses GraalVM Native Image and JavaFX. At the end of this tutorial, you will find links to materials discussing other options.

How to build an Android app with GraalVM Native Image

Requirements

We will use Linux. On Windows, it is possible to follow this tutorial and get a working Android app if you use WSL2. For building, we will use Maven.

Download the latest GraalVM, Community Edition based on Java 11, from here. Set it up as your JVM by creating an environment variable GRAALVM_HOME pointing to the GraalVM home directory, by setting the environment variable JAVA_HOME to ${GRAALVM_HOME}, and by adding ${GRAALVM_HOME}/bin to your PATH. If you are using Bash, add the following lines to your ~/.bash_profile:

export GRAALVM_HOME=<path to GraalVM home directory>
export JAVA_HOME=$GRAALVM_HOME
export PATH=$GRAALVM_HOME/bin:$PATH

When you type in java -version it should display something like this now:

> java -version
openjdk version "11.0.10" 2021-01-19
OpenJDK Runtime Environment GraalVM CE 21.0.0 (build 11.0.10+8-jvmci-21.0-b06)
OpenJDK 64-Bit Server VM GraalVM CE 21.0.0 (build 11.0.10+8-jvmci-21.0-b06, mixed mode, sharing)

(The GraalVM version may differ)

Type native-image to check if it’s already there on the path. If not, install it with:

gu install native-image

gu should be available now in your console because of $GRAALVM_HOME/bin in your PATH. Also, read this and install whatever you need.

You will need adb, “Android Debug Bridge”, to connect to your Android device and install the app on it. Here you can find more on how to do it.

Make sure your gcc is at least version 6. You can try following these steps. On top of that, you will need some specific C libraries (like GTK) to build the native image, and it varies from one computer to another, so I can’t tell you exactly what to do. But it shouldn’t be a big problem. Just follow error messages saying that you lack something and google how to install them. In my case this was the list:

  libasound2-dev (for pkgConfig alsa)
  libavcodec-dev (for pkgConfig libavcodec)
  libavformat-dev (for pkgConfig libavformat)
  libavutil-dev (for pkgConfig libavutil)
  libfreetype6-dev (for pkgConfig freetype2)
  libgl-dev (for pkgConfig gl)
  libglib2.0-dev (for pkgConfig gmodule-no-export-2.0)
  libglib2.0-dev (for pkgConfig gthread-2.0)
  libgtk-3-dev (for pkgConfig gtk+-x11-3.0)
  libpango1.0-dev (for pkgConfig pangoft2)
  libx11-dev (for pkgConfig x11)
  libxtst-dev (for pkgConfig xtst)

The example app

if you reached this point and everything seems to work, it means you probably should be able to compile and run the example app called HelloScala. HelloScala is based on HelloGluon from Gluon samples. Gluon is a company that maintains JavaFX and provides libraries that give us a layer of abstraction between our code and the device — be it desktop, Android, or iOS. It has some interesting implications: for example, you will see in the code that we check if we are on the desktop instead of Android, because if yes then we need to provide window size for our app. If we are on Android, we can just let the app’s window take the whole screen. If you decide to write something more complex with this tech stack, you will quickly see that you can use Gluon’s libraries and JavaFX (maybe together with ScalaFX) to achieve the same results other developers get by tinkering with Android SDK, while you are writing code that can be easily re-used on other platforms as well.

In the pom.xml of HelloScala you will find a list of plugins and dependencies our example app uses. Let’s take a look at some of them.

  • We will use Java 16 and Scala 2.13.
  • A tiny Scala library which resolves this problem in the interaction between Scala 2.13 and GraalVM Native Image.
  • For the GUI we will use JavaFX 16.
  • We will use two Gluon libraries: Glisten and Attach. Glisten enriches JavaFX with additional functionality specifically designed for mobile applications. Attach is an abstraction layer over the underlying platform. For us, it means we should be able to use it to access everything on Android from the local storage to permissions to push notifications.
  • scala-maven-plugin lets us use Scala in Maven builds (well, d’oh). Thank you, David!
  • gluonfx-maven-plugin lets us compile Gluon dependencies and JavaFX code into a native image. In its configuration you will find the attachList with Gluon Attach modules we need: device, display, storage, util, statusbar, and lifecycle. From those we will use directly only display (to set the dimensions of the app’s windows in case we run the app on a desktop and not in the full-screen mode on a mobile) and util (to check if we run the app on a desktop or a mobile), but the others are needed by these two and by Gluon Glisten.
  • javafx-maven-plugin which is a requirement for gluonfx-maven-plugin.

The code

HelloScala is just a simple example app — the actual Scala code only sets up a few widgets and displays them. The Main class extends MobileApplication from the Glisten library and then construct the main view programmatically, in two methods: init() for creating the widgets, and postInit(Scene) for decorating them. Since we want to test the app on our laptop before we install it on a mobile, we use postInit also to check on which platform the app is being run, and if it’s a desktop, we set the dimensions on the app’s window. In the case of a mobile it’s not necessary — our app will take the whole available space on the screen.

Another way to set up and display widgets in JavaFX is to use a WYSIWYG editor called Scene Builder which generates FXML files, a version of XML, that you can then load into your app. You can see how it is done in another example: HelloFXML. For more complex applications, you will probably mix those two approaches: FXML for more-or-less static views and programmatically set up widgets in places where the UI within one view changes in reaction to events (think, for example, of a scrollable list of incoming messages).

How to run the app

Building an Android native image takes time, so we want to avoid doing it too often. Even before running the app for the first time, you should invest some time in unit, component, and integration tests, so that if you change something in your app you could still be sure it works correctly even before any manual testing. Then, to check how your GUI looks like and works, use:

mvn gluonfx:run

If everything looks fine, build the native image… but first, for your desktop:

mvn gluonfx:build gluonfx:nativerun

After all, we work on a cross-platform solution here. Unless you want to test features of your app that will only work on a mobile device, you can first run it as a standalone desktop application. This will again let you test some layers of the app without actually running it on an Android device. And then, if all looks good, or if you decide to skip this step:

mvn -Pandroid gluonfx:build gluonfx:package

Successful execution of this command will create an APK file in the target/client/aarch64-android/gvm directory. Connect your Android phone to the computer with a USB cable, give the computer permission to send files to the phone, and type adb devices to check if your phone is recognized. It should display something like this in the console:

> adb devices
List of devices attached
16b3a5c8	device

Now you should be able to install the app on the connected device with adb install <path to APK> and a moment later you should see a new icon on your device’s main screen. When you click on the icon, it should open approximately the same screen as the desktop version of your app.

Installation might not work for a number of reasons, one of the most popular being that your Android simply does not allow installing apps this way. Go to Settings, find “Developers options”, and there enable “USB debugging” and “Install via USB”.

If everything works, and you see the app’s screen on your device, type adb logcat | grep GraalCompiled to see the log output. Now you can click the button with the magnifying glass icon on the app’s screen, and you should see "log something from Scala" printed to the console. Of course, before you write a more complex app, please look into plugins in the IDE of your choice that can display logs from adb logcat in a better way. For example

Here’s a screenshot of what the app looks like when you open it.

Next Steps and Other Useful Reading

If you managed to build one of the example apps and want to code something more complex, there are at least a few ways you can learn how to do it:

Contributors to this page: