OCaml + Cordova = more secured, typed and hybrid mobile applications.
by Danny Willems
Since several months, I began to be interested in mobile development. I found a job in Brussels in July 2015 where I learned how to develop hybrid mobile applications (one code = available for multiple mobile platforms) with Cordova and web technologies. I found it very interesting and after several month I continue to develop applications with this technology. I discovered a very great community about mobile development and some awesome frameworks like Ionic.
The majority of these frameworks use JavaScript as programming language but I don’t really like this language because you have no types, some weird things (equality between string and integer), parameters are sent as undefined if not passed, etc. I don’t really like to develop applications with JavaScript because it’s very ugly (even if I think it’s OK for prototyping, but not in production).
I discovered OCaml at the university, a very powerful programming language with inferred static type, type checking at compilation time, an extraordinary community and… a compiler from OCaml to JavaScript! So, I wanted to use this language to develop mobile applications with Cordova: it will be my university project for a semester.
The goal of my project is to be able to use native components of smartphones such like accelerometer, camera, send sms, etc in OCaml.
What are Cordova, js_of_ocaml and gen_js_api?
- Cordova allows you to develop hybrid mobile applications using web technologies such as HTML, CSS and JavaScript. For more information, see the official website. Through Cordova plugins, you can access to the native components. To learn how to make Cordova plugins, see the official tutorial. You can find the official Cordova plugin list here.
- js_of_ocaml provides a compiler from OCaml to JavaScript. Since Cordova applications use JavaScript, js_of_ocaml provides a way to develop mobile application using OCaml. For more info, see the Ocsigen project which contains js_of_ocaml
- gen_js_api aims at simplifying the creation of OCaml bindings for JavaScript libraries. It must currently be used with the js_of_ocaml compiler, although other ways to run OCaml code “against” JavaScript might be supported later with the same binding definitions (for instance, Bucklescript, or direct embedding of a JS engine in a native OCaml application)
All bindings are developed with gen_js_api and aims to be functional, typed and very close to the JavaScript interface.
How can I use a binding?
Needs compiler >= 4.03.0
I created a GitHub listing all bindings: ocaml-cordova-plugin-list. Each binding is an opam package and so can be installed with opam. It is recommended to add this repository as a remote opam package provider with
opam repository add cordova https://github.com/dannywillems/ocaml-cordova-plugin-list.git
Each binding can now be installed. For example, the binding to the camera plugin is cordova-plugin-camera. So, if you want to install the camera binding, you need to use
opam install cordova-plugin-camera
The appropriate opam package is given in the appropriate GitHub repository (list is given here).
If the plugin needs the binding to the standard js library such as device-motion, you need to pin the ocaml-js-stdlib first. If the plugin needs it, it is mentioned in the GitHub repository.
If you don’t want to add this repository, you can manually pin each repository.
What about documentation for each bindings?
Bindings interface are very close to initial plugins JavaScript interface. For example, for the cordova-plugin-camera allowing you to take a picture through navigator.camera.getPicture JavaScript function, you use Cordova_camera.get_picture OCaml function. The equivalent OCaml code to
var success_callback = function(success) {
console.log(success);
}
var error_callback = function(error) {
console.log(error);
}
var options = {quality: 25; destinationType: Camera.DestinationType.DATA_URL}
navigator.camera.getPicture(success_callback, error_callback, options)
is
let success_callback success = Jsoo_lib.console_log success in
let error_callback error = Jsoo_lib.console_log error in
let options =
Cordova_camera.create_options
~quality:25
~destination_type:Cordova_camera.Data_url
()
in
Cordova_camera.get_picture success_callback error_callback ~opt:options ()
(supposing Jsoo_lib.console_log is the binding to console.log function, see jsoo_lib). Most functions are implemented with optional arguments and these arguments are at the end of the arguments list, so unit is often mandatory.
As the OCaml interface is very close to JavaScript interface, no OCaml documentation is done yet. Feel free to contribute.
Most of bindings have an example application showing you how to use it. Bindings which don’t have example application are not tested. Please give a feedback about it and open issues if it’s the case.
You can find more information about this project on ocaml-cordova-plugin-list GitHub repository.
Be careful: device_ready event
Most of plugins create new objects which are only available when the deviceready event fires. You need to have as first lines:
let on_device_ready () =
(* Your code using plugins here *)
let _ = Cordova.Event.device_ready on_device_ready
The module Cordova comes from the binding to the cordova object so you need to add it for each project. This module can be installed with
opam install cordova
Could you give an entire example please?
Of course. I will show you how to write a hybrid application allowing you to send a SMS to someone. If we succeed to send the SMS, a dialog appears saying everything is OK, else a dialog shows an error message. You can find all the code in this repository
Other examples can be found here. Here some other screenshots.
Set up the development environment
First thing to do is to set up the development environment. What we need is:
- Cordova distributed as a NodeJS package. To install NodeJS, I recommend to use nvm. Read the GitHub instructions to install nvm and a NodeJS version. After that, install globally Cordova with
npm install -g cordova
- OCaml 4.03.0 and opam. See ocaml.org to install OCaml and opam for your distribution. After that, install OCaml 4.03.0 by using
opam switch 4.03.0
Now we have a basic development environment. We will install all Cordova plugins we need and all opam package (included bindings to Cordova plugins).
Create the Cordova project and install the needed plugins
Create a Cordova project by using
cordova create ocaml-cordova-plugin-sms-example
A basic Cordova project is created in the ocaml-cordova-plugin-sms-example directory. Go in this directory with
cd ocaml-cordova-plugin-sms-example
In this directory, you find a www directory which will be included in the final package you will install on the smartphone. It works exactly like a website. It contains a index.html file which is the first executed file (Cordova uses a WebView in which web files are executed, like a standard website).
We have to add the platform we need to build for. If you want to build for iOS (you need Mac OS X with XCode installed), use
cordova platform add ios
If you want to build for Android, you need to install the Android SDK. See the official documentation for the entire installation process. To add the Android platform to the Cordova project, use
cordova platform add android
I don't give the example for Windows/Windows Phone because it's very hard to install natively OCaml on Windows and you need to be on Windows to build for Windows/Windows Phone. However, with Bash on Windows eveything is working like on a common Linux Distrbution.
Now we install plugins. For this example, we will used two plugins: cordova-plugin-sms to send the message and cordova-plugin-dialogs to show a dialog. We can install with these commands:
cordova plugin add cordova-plugin-sms
cordova plugin add cordova-plugin-dialogs
OCaml and opam packages
We need js_of_ocaml (to compile our source file in JavaScript) and gen_js_api (to compile the bindings). We install it with opam with
opam install js_of_ocaml gen_js_api
After that, we add the opam package provider listing all bindings OCaml to the Cordova plugins available.
opam repository add cordova https://github.com/dannywillems/ocaml-cordova-plugin-list.git
As described in the repository, the bindings to cordova-plugin-sms and cordova-plugin-dialogs are available in the opam package cordova-plugin-sms and cordova-plugin-dialogs. So we use:
opam install cordova-plugin-sms cordova-plugin-dialogs
Due to the deviceready event, we need the binding to the Cordova object (as said in GitHub repository). We install it with
opam install cordova
I will also use some functions coming from jsoo_lib, a small library I write with bindings to standard JavaScript functions. You need to pin it and install it with opam
opam pin add jsoo_lib https://github.com/dannywillems/jsoo-lib.git
And now we have finished with the set up development environment. Go into code!
index.html and design
We use materializecss as a CSS framework. We move in the www directory and we use npm to install it:
cd www && npm install materialize-css
For the design, we need two inputs and a button to send the message. We replace the current index.html content with this following:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Security-Policy" content="default-src 'self' data: gap: https://ssl.gstatic.com 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src *">
<meta name="format-detection" content="telephone=no">
<meta name="msapplication-tap-highlight" content="no">
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width">
<link rel="stylesheet" type="text/css" href="css/index.css">
<!-- CSS setup for materialize -->
<link rel="stylesheet" type="text/css" href="node_modules/materialize-css/dist/css/materialize.min.css">
<script type="text/javascript" src="cordova.js"></script>
<script type="text/javascript" src="js/main.js"></script>
<title>OCaml Cordova Plugin: Sms</title>
</head>
<body>
<script type="text/javascript" src="node_modules/jquery/dist/jquery.min.js"></script>
<script type="text/javascript" src="node_modules/materialize-css/dist/js/materialize.js"></script>
<div class="row">
<form class="col s12">
<div class="row">
<div class="input-field col s12">
<input id="num" type="tel" class="validate">
<label for="num">Phone number</label>
</div>
<div class="input-field col s12">
<input id="msg" type="text" class="validate">
<label for="msg">Your message</label>
</div>
<div class="input-field col s12 center">
<button class="btn waves-effect waves-light" id="submit" type="submit" name="action">Send<i class="material-icons right">send</i></button>
</div>
</div>
</form>
</div>
</body>
</html>
To have the Material Icons, we need to write a CSS file. We also add a padding-top. Remove the entire content of css/index.css and insert the following code
body {
padding-top: 25px;
}
/* fallback */
@font-face {
font-family: 'Material Icons';
font-style: normal;
font-weight: 400;
src: local('Material Icons'), local('MaterialIcons-Regular'), url(../fonts/material_icons.ttf) format('ttf');
}
.material-icons {
font-family: 'Material Icons';
font-weight: normal;
font-style: normal;
font-size: 24px;
line-height: 1;
letter-spacing: normal;
text-transform: none;
display: inline-block;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
-webkit-font-feature-settings: 'liga';
-webkit-font-smoothing: antialiased;
}
We also download the ttf file and put in www/fonts directory.
mkdir -p fonts
wget https://fonts.gstatic.com/s/materialicons/v17/2fcrYFNaTjcS6g4U3t-Y5StnKWgpfO2iSkLzTz-AABg.ttf -O fonts/material_icons.ttf
As you can see in the index.html, we add a script named js/main.js: it will be the JavaScript code generated by js_of_ocaml ie our OCaml code compiled in JavaScript.
Now the best moment, the OCaml code!
The plugin cordova-plugin-sms defines a function Sms.send phonenumber message success_cb error_cb to send a message. The OCaml binding defines a module Cordova_sms and the binding to the Sms.send function send which uses labeled arguments and can be used in this way
Cordova_sms.send ~num:phonenumber ~msg:message ~succ_cb:success_cb ~err_cb:~error_cb
Here the logic: the user writes the phonenumber he wants to send the SMS to in the phonenumber input and the SMS content in the message input. When he touched the submit button, we get the contents of these input and call the function Cordova_sms.send with the right arguments.
Here the OCaml code:
let on_device_ready () =
let num_node = Jsoo_lib.get_input_by_id "num" in
let msg_node = Jsoo_lib.get_input_by_id "msg" in
let btn_node = Jsoo_lib.get_button_by_id "submit" in
let succ () =
Cordova_dialogs.alert "Message sent!" ~title:"It’s working!" ();
num_node##.value := (Js.string "");
msg_node##.value := (Js.string "")
in
let err msg =
Cordova_dialogs.alert msg ~title:"Something wrong =(:" ()
in
btn_node##.onclick := Dom.handler
(
fun e ->
let num = Js.to_string (num_node##.value) in
let msg = Js.to_string (msg_node##.value) in
if num = "" then
Cordova_dialogs.alert "Please enter a phone number." ~title:"Missing field" ()
else if msg = "" then
Cordova_dialogs.alert "Please enter a message." ~title:"Missing field" ()
else
Cordova_sms.send ~num:num ~msg:msg ~succ_cb:succ ~err_cb:err ();
Js._false
)
let _ = Cordova.Event.device_ready on_device_ready
Copy and paste this code in a file named test.ml
Compile the OCaml code in JavaScript
It’s time to compile our code in JavaScript. For that, we use js_of_ocaml. As we use some opam package, we will use ocamlfind and the -package argument to link all packages. Js_of_ocaml needs an OCaml bytecode, so first we compile the code in bytecode. Second, we use js_of_ocaml and output the JavaScript in js/main.js.
ocamlfind ocamlc
-o test.byte \
-no-check-prims \
-package js_of_ocaml \
-package js_of_ocaml.ppx \
-package gen_js_api \
-package jsoo_lib \
-package cordova \
-package cordova-plugin-sms \
-package cordova-plugin-dialogs \
-linkpkg test.ml
js_of_ocaml -o js/main.js +gen_js_api/ojs_runtime.js test.byte
Build the resulting application and run it
Now we can build the application. Depending on the platform you want to build for, you use (for Android)
cordova build android
or (for iOS)
cordova build ios
You can finally run the application on the emulator (or on your smartphone if you connected it) by using (for Android)
cordova run android
or (for iOS)
cordova run ios
Conclusion
I hope you liked this tutorial and I invite you to take a deeper look at the ocaml-cordova-plugin-list repository. You have the entire list of bindings and some examples.
Don’t hesitate to star the project and give a feedback!
tags: Mobile application - Cordova - OCaml - FP - RSS