Or at least we will talk today about one of a few ways to do it. If you want to learn more about what is possible, and why it might be a good idea to consider an alternative to standard Android app development, please take a look a look at this article on Scala on Android or watch the associated conference talk.
But today we will talk about how to use GraalVM Native Image to compile Scala code to a file executable on Android, with Gluon libraries to access the Android platform beneath, and JavaFX for Graphical User Interface. Scala is in a way just a cherry on the top of the cake here — with GraalVM you can choose from a wide array of programming languages. It probably makes sense to stick to JVM languages if you want to code for the Android platform, but Scala is still just one of the options. One reason I could give as to why you should consider it is that it allows for writing complex logic in concisely and elegantly, and since GraalVM Native Image is already a pretty heavy-weight player, you probably wouldn’t use it if you wanted to write only a light front-end for your CRUD app. It shows what its ahead-of-time compilation is capable of only if the code you write is complex enough. And Scala is a great choice for writing complex code.
- You will need a good computer. Haha, but for real. Building a native image of even a minimal example app takes a while and can eat all the RAM that you still have after Intellij is done with it.
But don’t run away in fear just yet. Before you are ready to let GraalVM take over your computer and compile that Android APK, you can use the JavaFX maven plugin to just quickly compile the GUI and run the app on your laptop. We will talk about it in a moment.
2. We will use Linux. As far as I know, for a moment Android native images work only on Linux machines. If you use a Mac or Windows and you are still able to follow this tutorial and get a working Android, please contact me and describe how you did it. Seriously. I want to know. (update: Ivan reported that it works on Windows with WSL)
3. Download GraalVM, Community Edition based on Java 11, from here.
4. Add this to your
export GRAALVM_HOME=<path to GraalVM home directory>
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)
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.
6. 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. Oh, and if it's not clear yet, you will need an Android device 😉
7. 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)
Okay, if you reached this point and everything seems to work, it means you probably should be able to compile and run one of the example apps:
Whichever you choose, just clone the whole GitHub repository, and go to one of the subfolders. HelloGluon is based on HelloGluon from Gluon samples. HelloFXML is based on… HelloFXML from the same repo, but I simplified it — I removed all Gluon dependencies, leaving only bare JavaFX and the Gluon’s
client-maven-plugin, to show what is the bare minimum for an Android app made this way. In both cases, we use Maven — not SBT, which is the standard Scala build tool — because we need Maven plugins that don't have their SBT equivalents yet, as far as I know (I plan to make research about it soon). Install
mvn if you don't have it yet.
In the case of HelloGluon (but not HelloFXML), we will create the app with the help of Gluon Mobile — a platform which uses JavaFX to build Java client apps. It looks like this will be the way to build Android apps with GraalVM in foreseeable future. No standard Android widgets — JavaFX instead. It has some interesting implications: the layer of abstraction we create between ourselves and the Android platform is not just a helpful tool, it’s a requirement. We won’t be able to access Android SDK — either at all or unless we get involved in some serious hacking which I don’t recommend. Instead, the idiomatic way here is to write your code platform-agnostic and access the platform’s characteristics only when it’s actually needed. For example, you will see in the HelloGluon 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. (But please note I don’t claim it will be completely platform-independent right away — just easy to re-use).
pom.xml of HelloGluon and HelloFXML, you will find a list of plugins and dependencies the apps use. Let's take a look at some of them.
- We will use Java 16 and Scala 2.13. Not much to say here apart from that I’m very happy I see this version number here. In my work with Android SDK, I still have to use Scala 2.11 and the Android version of Java, which is still something in-between 7 and 8.
- A tiny Scala library which resolves this problem in the interaction between Scala 2.13 and GraalVM Native Image. Thank you, Ólafur!
- For the GUI we will use JavaFX 16.
- In the case of HelloFXML, the javafx-fxml library is needed to let us handle FXML files. FXML is one way to describe the layout of GUI widgets in JavaFX. The other is to put them there directly in code, which is what we do in HelloGluon. Both have their pros and cons. FXML files can be generated by a WYSIWYG editor called Scene Builder, which is very useful, but on the down side it uses reflection to connect the generated view with the code.
- In the case of HelloGluon, we will use two 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. I want to explore this with example apps and tutorials because if any of these features don’t work well and there are no easy alternatives, it means the whole tech stack is not mature yet. But from what I saw so far I’m carefully optimistic.
- javafx-maven-plugin lets us use
mvn javafx:runcommand and quickly test changes to the app's GUI.
- scala-maven-plugin lets us use Scala in Maven builds (well, d’oh). Thank you, David!
- client-maven-plugin lets us compile Gluon and JavaFX code into a native image. In the case of HelloGluon, you will find here a list of Gluon dependencies. HelloFXML does not need them but instead it requires a
reflectionListtag with the name of a controller for the view generated from the FXML file.
In both example apps the actual Scala code only sets up a few widgets and displays them. In HelloGluon, the
Main class extends
MobileApplication from the Glisten library and then construct the main view programatically, in two methods:
init() for creating the widgets, and
postInit(Scene) for decorating them. In HelloFXML it's even simpler: the
Main class extends
Application from JavaFX (Glisten's
MobileApplication is a subclass of this one) and in its
start(Stage) method we load the scene from the FXML file. The FXML file describes two widgets — a button and a label — and it points to
HelloFXMLController as the controller for those widgets. In there we can initialize them and react to button clicks. (For the details, please follow the links in this paragraph).
If you want to write any more complex application, you will probably mix those two approaches: FXML for more-or-less static views and programatically 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). Also, the choice should be affected by how much of the GUI code you want to cover with tests. I would suggest to build reasonably light GUI controlled by Scala classes which don’t rely on Gluon or JavaFX and therefore can be unit-tested — but this is a topic for another article.
By the way, at this point you should be able to easily see how you can delete everything referring to Scala from pom.xml and still write an app on Android in Java 16 (which is cool as well — we can’t do that in the standard Android SDK). It shows one thing that is very impressive to me. To write an Android app this way, I put together work of many brilliant people who do not necessarily even know about each other. Some of them work on Scala libraries, some on GraalVM, others on Gluon libraries, and JavaFX. All of them create new features and fix bugs for a lot of reasons, not really caring about that somewhere there someone wants to write an Android app in Scala. Yet, it all fits together. And it works the other way around too: by writing Android apps in Scala with all this tech stack, we test and experiment with all the puzzle pieces. We can provide feedback for their makers and maybe even help to fix some bugs or write some code that will be useful to someone else, someone who never thought of writing Android apps in Scala.
How to run the app
As I mentioned already, 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:
If everything looks fine, build the native image… but first, for your desktop:
mvn client:build client:run
After all, we are cross-platform 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 client:build client: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 an 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
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 you can’t find “Developers options” anyway then it might mean you need to do something like tapping the field with your Android OS version number ten times (or just google it).
If everything works and you see the app’s screen on your device, type
adb logcat | grep GraalCompiled to see the log output. Now, for example, if you installed HelloGluon, 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, for anything more complex, please look into plugins in the IDE of your choice that can display logs from adb logcat in a more user-friendly way.
And that’s it. Where do we go now?
Hey, you made it to this point! It’s already quite far.
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:
- Read more and experiment with JavaFX. You can start with its official documentation and with this huge list of tutorials by Jacob Jenkov.
- Install Scene Builder and learn how to build GUI with it. Apart from the docs, you can find a lot of tutorials about it on YouTube.
- Look through Gluon’s documentation of Glisten and Attach to learn how to make your app look better on a mobile device, and how to get access to your device’s features.
- Download an example from Gluon’s list of samples and rewrite it to Scala. And when you do, let me know!
- Look into ScalaFX — a more declarative, Scala-idiomatic wrapper over JavaFX.
- Download some of the other examples from my “Scala on Android” repository on GitHub. Contact me, if you write an example app of your own and want me to include it.
- Join us on the official Scala discord — we have a #scala-android channel there
- There is also an #android channel on the “Learning Scala” discord.
- Finally, if you have any questions, you can always find me on Twitter