I’ve been playing around with Calatrava recently, and it’s been pretty interesting. The most exciting aspect of it is their approach to cross platform. They push for re-usable middleware (e.g., shared applicaiton logic) while leaving the UI layer native.
Interestingly enough, the shared logic is in CoffeeScript. Thusly it can even be shared across other platforms such as Ember.js and Node.js – provided the logic was reusable enough by nature.
My only complaint so far is the project’s poor documentation. For example, Calatrava supports native UIs on both iOS and Android, but the sample app generated by running calatrava create MYAPP
only shows a native UI on iOS. The Android app generated is using a web view.
For starters, you’ll need to change the ConversionForm
class (located in droid/MYAPP/src/com/MYAPP/
) to inherit from com.calatrava.bridge.RegisteredActivity
. So, this:
Becomes:
Next, you’ll mostly gut the existing implementation of ConversionForm
(leave the getPageName
method, everything else goes) and override getFieldValue
and render
:
The getFieldValue
method serves as a bridge between CoffeeScript and native. Specifically it allows Calatrava to know what values are currently on the screen. So, for a text field, it might look like:
((EditText)this.findViewById(R.id.my_field)).getText().toString();
So, in order to write this method you’ll need to create a layout file. Call it conversion_form.xml
and place it under droid/res/layout
:
Now revisit the action, and override onCreate:
If you run the app right now by running rake droid:deploy
you’ll be greated with your custom view, but it’s not functional yet. With that out of the way, let’s revisit our getFieldValue
method.
If you look at the iOS sample app you’ll get an idea of how this method should be implemented:
Alternatively you could have looked at controller.converter.coffee
under kernel/app/converter
and looked for all calls to views.conversionForm.get
to see which view controls are referenced by this controller. In this case, it’s referencing:
- in_currency
- out_currency
- in_amount
Let’s wire those up to our layout by implementing the getFieldValue
method:
For now, let’s just hard code the input and output currency type. We have, however, implemented the in_amount
field so we are actually bridging our native view to Calatrava (albeit just in one direction). In order to fire events in the CoffeeScript view controller, we’ll use the com.calatrava.bridge.RegisteredActivity.triggerEvent(String event, String... extraArgs)
method. Luckily for us, our activity is a RegisteredActivity
and we can therefore call triggerEvent
on this
. Doing this will directly call a CoffeeScript method, passing in any arguments. In our case we want to call the convert
method, and we don’t need to pass in any arguments. Wrapping this up in a similarly named method gives us:
And, finally, the last piece of the puzzle is to fully implement the render
method so that values can be passed from Calatrava into your native view. On Android Calatrava will supply a String
containing JSON, it’s up to the implementation of Render
to parse it. Other than that small difference between iOS and Android, looking again at the ConversionFormView.m
reveals the intent of this method:
Looping over the key/values, the render
method will attempt to match each key to the name of a UI element and bind it’s value. An implementation for Android would look like:
Unfortunantly running this code as-is results in a runtime error. Seemingly Calatrava calls render
from a non-UI thread, resulting in the following crash:
E/AndroidRuntime(13246): org.mozilla.javascript.WrappedException: Wrapped android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views. (calatrava/scripts/bridge.js#34)
A work around for this issue is to wrap the UI updates in a Runnable to ensure they’re performed on the main thread. To keep the code clean, you can break the render
implementation out into a secondary method (I called mine setFieldValue
):
At this point you should have a functional native UI, albeit hard coded to only convert USD to AUD (click here to see what the current ConversionForm.java looks like). In a future post I’ll continue with implementing this native UI.