Ceylon already runs on the JVM, whether bare-bones, via JBoss Modules, Vert.x, Java EE Servlet
containers such as WildFly, or OSGi containers, as well as on JavaScript VMs such as Node.js
and the browser. But today we're going to explain how to run Ceylon on mobile devices, not just
in the browser (though it does play a part in it), but as applications, via Apache Cordova.
Apache Cordova allows you to write applications for every mobile
platform, including Android and iOS, using nothing but HTML, CSS and JavaScript. Since Ceylon
compiles to JavaScript this is perfect as it allows us to run our Ceylon applications on iOS,
via the JavaScript compiler backend.
Note that this article is using Ceylon 1.2.3 which is not yet released, because the JavaScript
runtime in Cordova on Android had one peculiarity we had to work around in the language module
JavaScript implementation. Luckily you can get nightly builds of
Ceylon 1.2.3 and the
Ceylon 1.2.3 SDK.
First, install Apache Cordova and add two platforms.
I haven't been able to test the iOS platform since it requires an OSX platform to build and
an iOS device to test, and I lack both, so I will explain how to package for Android and the
browser, and let you guys try it out for iOS, but I have enough faith in Apache Cordova that
it will Just Work™.
$ sudo apt-get install npm
$ npm install cordova
Small note: for me this installed things in ./node_modules/cordova
and the Apache Cordova
command in ./node_modules/cordova/bin/cordova
, so adapt your path as you must.
$ cordova create ceylon-cordova-demo
$ cd ceylon-cordova-demo
$ cordova platform add browser
$ cordova platform add android
At this point you have your application ready to be checked in your browser:
$ cordova platform run browser
Or in an Android emulator, provided you have downloaded the Android SDK already:
$ ANDROID_HOME=.../Android/Sdk cordova platform build android
$ ANDROID_HOME=.../Android/Sdk cordova platform run android
Writing an application using just HTML and CSS means you have to make some effort for
it to look good, and instead I decided to delegate to use Polymer
so that my application would have the look and feel of
Android Material Design applications
to feel even more like a native application on Android. No doubt a similar look and feel exists for iOS.
So let's download Polymer in our
application's HTML sources:
$ npm install bower
$ cd www
$ bower init
$ bower install --save Polymer/polymer
$ bower install --save Polymerelements/paper-item
And now edit www/index.html
to use Polymer:
<script src="bower_components/webcomponentsjs/webcomponents.js"></script>
<link rel="import" href="bower_components/paper-item/paper-item.html">
<link rel="import" href="bower_components/paper-item/paper-item-body.html">
You should also remove the default CSS:
<link rel="stylesheet" type="text/css" href="css/index.css">
Ceylon compiles to JavaScript modules by way of require.js, so we're going to
have to download it too:
$ cd www/js
$ wget http://requirejs.org/docs/release/2.2.0/minified/require.js
Our Ceylon demo will use jQuery
to add elements to the HTML page, so we also need it:
$ cd www
$ bower install --save jquery
Now edit www/index.html
to use both:
<script type="text/javascript" src="js/require.js"></script>
<script src="bower_components/jquery/dist/jquery.js"></script>
We're going to write a trivial application that queries Ceylon Herd for the list of modules, to display them
in a list.
Let's start by creating a Ceylon module in source/cordova/demo/module.ceylon
:
module cordova.demo "1.0.0" {
import ceylon.json "1.2.3";
}
And our application's main method in source/cordova/demo/run.ceylon
:
import ceylon.json { parseJson = parse, JsonObject = Object, JsonArray = Array }
shared void run() {
dynamic {
dynamic target = jQuery("#target");
void success(dynamic data){
assert(is JsonObject json = parseJson(data),
is JsonArray results = json["results"]);
for(res in results){
assert(is JsonObject res);
assert(is String name = res["module"],
is JsonArray versions = res["versions"]);
value versionText = ", ".join(versions.narrow<String>());
dynamic item = jQuery("<paper-item/>");
dynamic body = jQuery("<paper-item-body two-line/>").appendTo(item);
jQuery("<div/>").text(name).appendTo(body);
jQuery("<div secondary/>").text(versionText).appendTo(body);
target.append(item);
}
}
jQuery.get("https://modules.ceylon-lang.org/api/1/complete-modules?module=ceylon.", null, success, "text");
}
}
Now, obviously using jQuery
to add HTML is far from ideal, so I can't wait for someone to
extend ceylon.html to allow Polymer web
components!
We can now compile our application for JavaScript:
$ ceylon compile-js
And copy our compiled module and all its dependencies to where the Apache Cordova application
will find them in www/modules
:
$ ceylon copy --with-dependencies --js --out www/modules cordova.demo/1.0.0
Because we're going to use require.js
inline and connect to Ceylon Herd, we have to adjust the Apache
Cordova permissions in www/index.html
, so find that line and edit it as such:
<meta http-equiv="Content-Security-Policy"
content="default-src 'self' 'unsafe-inline'
https://fonts.googleapis.com
https://fonts.gstatic.com;
connect-src *">
We're left with just invoking our Ceylon function in www/index.html
:
<script type="text/javascript">
// tell require.js where our Ceylon modules are
require.config({
baseUrl:'modules',
});
// when the document is ready
jQuery(function(){
// load our Ceylon module
require(['cordova/demo/1.0.0/cordova.demo-1.0.0'], function(client) {
// and call our run method
client.run();
});
});
</script>
And setting up the target HTML elements where we're going to add every loaded module (in
the same file):
<body id="app" unresolved>
<app-shell class="fit">
<div id="target" role="listbox"></div>
</app-shell>
</body>
And that's it, try it out in your browser:
$ cordova platform run browser
Or in an Android emulator:
$ ANDROID_HOME=.../Android/Sdk cordova platform build android
$ ANDROID_HOME=.../Android/Sdk cordova platform run android
If you have OSX and iOS dev tools, please try this with the iOS Cordova platform and let
me know how it works :)
In the future, we would benefit from having a type-safe API in front of the Cordova JavaScript
API that lets you access native mobile APIs such as the camera, GPS, contacts, but even without
it you can already use them using dynamic
blocks.
And now for the teaser… this is only one method to run Ceylon on Android, because obviously it
may be more desirable to use the JVM compiler backend and integrate with the Android Tools to
run Ceylon on Android using only type-safe APIs. Don't worry, it's coming, and next week I will
show you how :)