ClojureScript on Android

July 15, 2015

Over the past few days, I had one of the most rewarding experiences in my development career, and I wanted to share the story.

We now have ClojureScript running on Android. Not in a WebView, or using something like Cordova, but actually running in an embedded JavaScript engine. What just happened?




Recently, I had done some work to create an iOS ClojureScript REPL app, Replete. This is all cool, but an Android equivalent was just begging to be created. Unfortunately, my Android fu had become so weak, I couldn't take it on.

But, Tahmid Sadik stepped up to the plate.

Tahmid could sling together a UI and he had Rhino instantiated and evaluating JavaScript statements, but the next step of getting bootstrapped ClojureScript running was a challenge.

But even before that, he had to bootstrap ClojureScript in his app. I'm using this word in a different sense, meaning that he needed to bring up the ClojureScript runtime by making use of Google Closure's dependency management system, without JavaScript concatenation, or any optimizations (i.e., :none mode), defining CLOSURE_IMPORT_SCRIPT as needed, etc. It is important to have the runtime in this mode when you plan on having a REPL involved. It also opens the door for allowing you to require source-defined namespaces in your REPL.

In fact, Replete needed to do the same, and it accomplished this using a feature of Ambly. Yes, I know, Replete is a standalone REPL and doesn't need Ambly. But I cheated and reused that capability, just to get ClojureScript up and running inside Replete.

As an aside, I had originally planned on building Replete using React Native. It turns out that using ClojureScript support for React Native, which is early days, and bootstrapped ClojureScript, which is early days, was just too much to take on while also succeeding in getting things actually working. So, for Replete, I fell back to keeping things simple, choosing to not even involve Goby.

A nice consequence of the above is that there were no React Native dependencies (which don't yet publicly exist for Android), nor Goby dependencies (which only targets iOS). The simplicity of bootstrapped ClojureScript inside Replete, working with a "conventional" iOS UI proved to be very useful in helping Tahmid accomplish the same on Android.

So, back to the story: Essentially Tahmid replicated (pun?) Ambly's bootstrap logic, carefully executing the same “launch sequence” of JavaScript statements in Rhino. Every now and then, he would hit an odd failure, and I would recall hitting the exact same thing months ago, and—with some quick consultation of the Ambly code—was able to advise a solution.

After getting through this, he was able to bring up ClosureScript.

cljs.core.apply.call(null,cljs.core.inc,new cljs.core.PersistentVector(null, 1, 5, cljs.core.PersistentVector.EMPTY_NODE, [1], null))

This is the JavaScript that would be associated with (apply inc [1])

And Tahmid got back 2.0. Woot! I think this may have been a first. ClojureScript running in embedded Rhino on Android.

Next, onto trying to use the reader, analyzer, and compiler. Now at this point, it was expedient to simply use the JavaScript that was emitted for use in Replete, and have the Android app evaluate Replete's read-eval-print function, passing in the string (+ 1 2). If that worked, then, bootstrapped ClojureScript baby!

replete.core.read_eval_print.call(null,'(+ 1 2)')

But… no dice. A call to goog.require('replete.core'); caused something funky to derail in Transit related to randomUUID. Dang! As these things go sometimes, more stuff evidently needed to be addressed.

No big deal, Replete was experimenting with different ways of loading the analysis cache, thanks to Karl Mikkelsen, and we had a version ready that just used plain JavaScript, with no dependencies. That worked, and after some work to set a print callback (so things like println could work), and Tahmid announced to me via Slack:

I got 3
(+ 1 2) = 3

... explitives on my end, inappropriate for a blog post. A bootstrapped ClojureScript REPL was just born on Android!

Tahmid wrapped up some things in the UI, and patched up a few minor things with the JavaScript/ClojureScript integration, and he announced Replicator to the world.

What a blast!

Now, Tahmid is working on replacing Rhino, which is a bit slow, with JavaScriptCore.

I think this will lead to some incredible speed boosts. This sets some important groundwork for our ability to interface with JavaScriptCore on Android which is what will be used for React Native.

From the big picture, I really think this ClojureScript thing on Android is going to fly, especially with JavaScriptCore. For a matter of perspective, have a look at the launch-time speed differences for Vladimir Iakovlev's port of Bocko to Android.

The above is on a Nexus 5 device. I think it shows the true promise of ClojureScript with respect to reducing computational latency on mobile devices. I think it is time for ClojureScript to come alive for use in developing apps targeting mobile devices!

Update Sept. 30, 2015: Corrected to indicate that Vladimir Iakovlev's Bocko tests are done on an actual Nexus 5 device (and not a simulator).

Tags: Android ClojureScript Bootstrap