Sunday, March 28, 2010

Getting Started with DroidScript

DroidScript is a tool for programming Android applications in JavaScript.

Start by installing the DroidScript app on your device. Scan this QR-code to install the app (use Barcode Scanner by ZXing, which is available at Android Market):

qrcode

Alternatively, download using this link (works in the browser on the device):
http://droidscript.se/DroidScript.apk

You can edit and run scripts on the device from a web browser with the DroidScript Live Editor: http://droidscript.se

The Live Editor contains some sample code and instructions to get you started.

You can also edit scripts directly on the device, but this is not yet fully developed; the editor is very primitive and you cannot save scripts, for example.

If you want to constribute to the project or browse the source code for the DroidScript app, the code is at GitHub: http://github.com/divineprog/droidscript

When you update the DroidScript app, you will also need to update the associated script files. Do this by seleting "Update" in the options menu. Then restart the DroidScript app.

Contact info:
Email: mikael.kindborg at gmail
Twitter: http://twitter.com/divineprog

Sunday, March 21, 2010

Custom ListAdapter in JavaScript on Android using a Java Proxy

At present, it is a big hazzle to program Android in purely interpreted languages, like JavaScript on the Mozilla Rhino engine. The catch is of course that Dalvik byte code generation is not (yet) implemented by any dynamic language engine, including Rhino. Still, it is possible to do all sorts of cool things with JavaScript on Android.

Today (which means Sunday, and in Stockholm is has been snowing!), I have been working on using Java Proxy as a way to dynamically implement interfaces in JavaScript. My need for this arose from wanting to implement a custom ListAdapter for use with a ListView (see previous post for some more background to the issues involved). It would be possible to implement everything that is needed in Java, but it is much more fun to stretch the limits of what is possible to do in JavaScript. And it is a great learning experience.

The interactive web based DroidScript editor has been invaluable when experimenting with JavaScript on Android. I have my phone next to the computer, edit and run code from Firefox on the computer, and interact with the result on the phone. This is a quite nice way to work. Error handling still leaves much more to wish for in DroidScript. It is sometimes tricky to find errors, and hooking up with a USB cable and using logcat is a big help to track down exceptions. Good, readable error reporting is an important area to improve upon. But, since you typically evaluate small pieces of code when developing incrementally, it is usually easy to spot errors.

The following is a complete example that can run from the DroidScript editor. Make sure the DroidScript server is running on the device, paste the code into the editor, select it, and then click "Run as Activity".
function onCreate(bundle)
{
    var lang = Packages.java.lang;
    var android = Packages.android;
    var widget = Packages.android.widget;
    var Typeface = Packages.android.graphics.Typeface;
    var Color = Packages.android.graphics.Color;
    
    var listView = new widget.ListView(Activity);
    var fruits  = ["Lemon", "Peach", "Plum"];
    
    listView.setAdapter(createListViewArrayAdapter(
        fruits,
        function(position, convertView) {
            var view = convertView;
            if (null == convertView) {
                view = new widget.TextView(Activity);
                view.setPadding(25, 15, 25, 15);
                var font = Typeface.create(
                    Typeface.SANS_SERIF, 
                    Typeface.BOLD);
                view.setTypeface(font);
                view.setTextSize(26);
                view.setBackgroundColor(Color.rgb(0, 0, 64));
                view.setTextColor(Color.rgb(255, 255, 255));
                // It is also possible to put actions on list items
                //view.setOnClickListener(function () {
                //    view.setText("You Clicked Me!"); })
            }
            view.setText(fruits[position]);
            return view; }));
    listView.setOnItemClickListener(function(parent, view, position, id) {
        showMessage("You picked: " + fruits[position]); });
    
    Activity.setContentView(listView);
}

// Display a Toast on the device
function showMessage(message)
{
    var Toast = Packages.android.widget.Toast;
    Toast.makeText(
        Activity,
        message,
        Toast.LENGTH_LONG).show();
}

// Convert a Java array to a JavaScript array
function javaArrayToJsArray(javaArray)
{
    var jsArray = [];
    for (i = 0; i < javaArray.length; ++i) {
        jsArray[i] = javaArray[i];
    }
    return jsArray;
}

// Create an instance of a Java interface
// javaInterface - the interface type
// handler - object that will be sent messages to the instance
function createInstance(javaInterface, handler)
{
    var lang = Packages.java.lang;
    var interfaces = lang.reflect.Array.newInstance(lang.Class, 1);
    interfaces[0] = javaInterface;
    var obj = lang.reflect.Proxy.newProxyInstance(
        lang.ClassLoader.getSystemClassLoader(),
        interfaces,
        // Note, args is a Java array.
        function(proxy, method, args) {
            // Convert Java array to JavaScript array
            return handler[method.getName()].apply(
                null,
                javaArrayToJsArray(args));
        });
    return obj;
}

// Creates a custom ListAdapter
// items - a JavaScript array
// viewFun - a function called to handle the creation 
// of views for the elements in the list
function createListViewArrayAdapter(items, viewFun)
{
    var lang = Packages.java.lang;
    var widget = Packages.android.widget;
    var observer;
    
    var handler = {
        areAllItemsEnabled : function() {
            return lang.Boolean.TRUE; },
        isEnabled : function(position) {
            return lang.Boolean.TRUE; },
        getCount : function() {
            return lang.Integer.valueOf(items.length); },
        getItem : function(position) {
            return items[position]; },
        getItemId : function(position) {
            return lang.Long.valueOf(position); },
        getItemViewType : function(position) {
            return lang.Integer.valueOf(0); },
        getView : function(position, convertView, parent) {
            return viewFun(position, convertView); },
        getViewTypeCount : function(position) {
            return lang.Integer.valueOf(1); },
        hasStableIds : function(position) {
            return true; },
        isEmpty : function(position) {
            return 0 == items.length; },
        // We can only have one observer!
        registerDataSetObserver : function(theObserver) {
            observer = theObserver; },
        unregisterDataSetObserver : function(theObserver) {
            observer = null; },
    };
    
    return createInstance(Packages.android.widget.ListAdapter, handler);
}

How to make a simple ListView

I have been frustrated about the fact that all of the handy adapter classes used with ListView take a resource id for the view to be used to present list items. When scripting Android, you typically do not create the layout using XML, rather you create widgets programatically. Why not put in a constructor that accepts a view factory rather than a resource id? (Note that it is perfectly possible to create a DSL for defining user interfaces in a declarative way in JavaScript, to replace XML definitions. By the way, anyone knows if there is a way to dynamically manipulate and update XML definitions on the device?).

Now, while experimenting with creating a custom ListAdapter in JavaScript, I discovered that there are predefined resources that can be used to specify the type of list view item. Very handy! (Still miss a constructor that accepts a view factory!)

Here is an example of how to create a sample application that displays a simple list view. Start the server in the DroidScript app, go to http://droidscript.se and enter the ip-address of the device, then paste the function below into the editor, select it, and click "Run as Activity", which will run the selected code as a new activity.
function onCreate(bundle)
{
    var lang = Packages.java.lang;
    var android = Packages.android;
    var widget = Packages.android.widget;

    var listView = new widget.ListView(Activity);

    var fruits = lang.reflect.Array.newInstance(lang.String, 3);
    fruits[0] = "Lemon";
    fruits[1] = "Peach";
    fruits[2] = "Plum";

    var arrayAdapter =
        new widget.ArrayAdapter(Activity,
           android.R.layout.simple_list_item_1,
           fruits);

    listView.setAdapter(arrayAdapter);

    Activity.setContentView(listView);
}

Sunday, March 14, 2010

DroidScript for Android 1.6

I have changed the version requirements so that DroidScript now should work with Android 1.6. Get the new version at http://droidscript.se

Also made some updates of the repository at http://github.com/divineprog/droidscript, the README-file is now up-to-date.