With all the features that RhoMobile 4 provides for you, such as access to the camera, barcode scanner or card reader, there are times where the functionality you need simply isn't there and you must resort to writing platform-native code. That process, dreaded by many, turns out not to be so hard after all. In many cases you can get away with writing very little code to achieve your needs, and it's just a case of knowing which steps to follow.

 

There is an extensive guide on writing native extensions available already but in this tutorial we will show the simplest thing that can possibly work, so that you can see the process demonstrated step by step.

 

Our future extension

 

It is often the case that the need for a native extension corresponds with the need to access some functionality that is only available in one particular platform. in this example, we will write an Android extension that tells us the path to the external SD card, so that our application can store data on it. As you may already know, in most handsets that path is "/sdcard", but there is no guarantee that the path is the same in all devices, so it is strongly discouraged to rely on hardcoded magic values. Instead, we will behave like good Android citizens and use the method provided by the platform to find out the proper path. In many cases your extensions will not require more than a few methods, so you can take this as an example of how to get started.

 

The native code

 

Because debugging native extensions gets complicated very quickly, you should first make sure that your native code works before converting it into an extension. In our case, the code required to get the path to the SD card is intentionally very simple:

 

File sdCardRoot = Environment.getExternalStorageDirectory();
String sdCardPath = sdCardRoot.getAbsolutePath();



 

 

All that remains now is how to integrate this code into a our very own RhoMobile native extension.

 

 

It all starts with an app

 

The "rhogen extension" command is in charge of generating an extension based on a predefined template that comes bundled with RMS 4.0, now is our turn to customize it. Because the official documentation already covers most of the process and options in some depth, the explanations in this tutorial will be brief, just enough to understand what's going on. You can probably solve 80% of your native extension needs by following this tutorial but remember that the more extensive documentation will help you cover the remaining 20% when you need it.

 

To make this tutorial self-contained, we will create a new application from scratch (but the same procedure applies to an existing application by skipping the "rhogen app" step).

 

From the command line, run

 

rhogen app sampleapp
cd sampleapp
rhogen extension externalstorage



 

In your application's top-level directory you will now have a new "extensions" subdirectory which, in turn, contains "externalstorage" where our new extension will live.

 

Every extension has an xml file called an API descriptor that describes (hence the name) which module and methods the extension provides. Open extensions/externalstorage/ext/externalstorage.xml and it will be similar to this:

 

<?xml version = "1.0"?>
<?xml-stylesheet type="text/xsl" href="pb_help.xsl"?>
<API>
    <MODULE name="Externalstorage" parent="Rho">
        <HELP_OVERVIEW>Example extension api</HELP_OVERVIEW>
        <MORE_HELP>This is example of API. Implementation contain in extension.</MORE_HELP>

        <TEMPLATES>
            <DEFAULT_INSTANCE/>
            <PROPERTY_BAG/>
        </TEMPLATES>


        <PROPERTIES >
            <DESC>list of properties supported by instance of object</DESC>
            <PROPERTY name="simpleStringProperty" type="STRING" usePropertyBag="accessorsViaPropertyBag" >
                <DESC>simple string property</DESC>
            </PROPERTY>
        </PROPERTIES>


        <METHODS>


            <METHOD name="enumerate" access="STATIC" hasCallback="optional">
                <RETURN type="ARRAY">
                    <DESC>Array of Externalstorage objects</DESC>
                    <PARAM type="SELF_INSTANCE"/>
                </RETURN>
            </METHOD>

            <METHOD name="getPlatformName">
                <DESC>return string with platform</DESC>
                <RETURN type="STRING"/>
            </METHOD>

            <METHOD name="calcSumm">
                <DESC>return summ of two params: a+b</DESC>
                <PARAMS>
                    <PARAM name="a" type="INTEGER">
                    </PARAM>
                    <PARAM name="b" type="INTEGER">
                    </PARAM>
                </PARAMS>
                <RETURN type="INTEGER"/>
            </METHOD>


            <METHOD name="joinStrings">
                <DESC>return join of two strings: a+b</DESC>
                <PARAMS>
                    <PARAM name="a" type="STRING">
                    </PARAM>
                    <PARAM name="b" type="STRING">
                    </PARAM>
                </PARAMS>
                <RETURN type="STRING"/>
            </METHOD>


        </METHODS>
   
        <USER_OVERVIEW>
        </USER_OVERVIEW>


        <VER_INTRODUCED>1.0.0</VER_INTRODUCED>
        <PLATFORM>
        </PLATFORM>
    </MODULE>
</API>



 

 

Although there are quite a few tags in there, you can figure out what most of them are for by their names, and the good news is, in our simple case we can remove most of them. Replace the contents of the original file with the following:

 

<?xml version = "1.0"?>
<?xml-stylesheet type="text/xsl" href="pb_help.xsl"?>
<API>
    <MODULE name="Externalstorage" parent="Rho">
        <HELP_OVERVIEW>Example extension api</HELP_OVERVIEW>
        <MORE_HELP></MORE_HELP>

        <TEMPLATES>
        </TEMPLATES>

        <PROPERTIES>
        </PROPERTIES>

        <METHODS>
            <METHOD name="getSDPath" access="STATIC">
                <PARAMS>
                </PARAMS>

                <RETURN type="STRING">
                    <DESC>Path of the SD card</DESC>
                </RETURN>
            </METHOD>                 
        </METHODS>

        <USER_OVERVIEW>
        </USER_OVERVIEW>

        <VER_INTRODUCED>1.0.0</VER_INTRODUCED>
        <PLATFORM>
        </PLATFORM>
    </MODULE>
</API>



 

We are left with a very simple file that tells us our extension will be accessed as Rho.Externalstorage in Javascript (Rho::Externalstorage in Ruby) and will have one static method called "getSDPath".

 

Every time you make changes to the API descriptor, run

 

cd extensions/externalstorage/ext
rhogen api externalstorage.xml



 

so that autogenerated files are brought in sync with your changes.

 

 

The natives are actually pretty calm and having fun

 

Time to implement our native code! Open extensions/externalstorage/ext/platform/android/generated/src/com/rho/externalstorage/IExternalstorageSingleton.java and you will see the interface that you native class must implement:

 

package com.rho.externalstorage;

import java.util.Map;
import java.util.List;

import com.rhomobile.rhodes.api.IMethodResult;

public interface IExternalstorageSingleton
{
    void getSDPath(IMethodResult result);
}



 

This file is autogenerated from the descriptor every time you make a change and it looks just like what you would expect, with the possible exception of the method being void instead of returning a value.

 

Now open extensions/externalstorage/ext/platform/android/src/com/rho/externalstorage/ExternalstorageSingleton.java

 

You will note that it says it implements the IExternalstorageSingleton you just saw, but it doesn't look like it actually does:

 

package com.rho.externalstorage;

import java.util.LinkedList;
import java.util.List;

import com.rhomobile.rhodes.api.IMethodResult;

class ExternalstorageSingleton extends ExternalstorageSingletonBase implements IExternalstorageSingleton {
    public ExternalstorageSingleton(ExternalstorageFactory factory) {
        super(factory);
    }

    List<Object> getIDs() {
        List<Object> ids = new LinkedList<Object>();
        ids.add("SCN1");
        ids.add("SCN2");
        return ids;
    }

    @Override
    protected String getInitialDefaultID() {
        return (String)(getIDs().get(0));
    }

    @Override
    public void enumerate(IMethodResult res) {
        res.set(getIDs());
    }
}



 

 

That is because this class is where your hand-written code goes. Note that the autogenerated interface was stored in .../android/generated/src/... while this is in .../android/src/... This means that the generator will never make changes to it, to make sure your code is never overwritten, and it is up to you make the appropriate changes to your implementation.

 

In our case, it will be very easy: we will remove all those methods we do not want, and add the one from the interface and any required import statements. Our class will look like this:

 

package com.rho.externalstorage;

import java.io.File;
import android.os.Environment;
import com.rhomobile.rhodes.api.IMethodResult;

class ExternalstorageSingleton extends ExternalstorageSingletonBase implements IExternalstorageSingleton {
    @Override
    public void getSDPath(IMethodResult res) {
        File sdCardRoot = Environment.getExternalStorageDirectory();
        String sdCardPath = sdCardRoot.getAbsolutePath();

        res.set(sdCardPath);
    }
}



 

Note that instead of returning a value from the method, you instead call IMethodResult.set and pass it the value you want to return (the official guide explains why this is done this way). In the same folder, open Externalstorage.java and remove all the methods in there except the constructor so that the file ends up looking like this:

 

package com.rho.externalstorage;

import java.util.Map;

import com.rhomobile.rhodes.api.IMethodResult;
import com.rhomobile.rhodes.api.MethodResult;

public class Externalstorage extends ExternalstorageBase implements IExternalstorage {

    public Externalstorage(String id) {
        super(id);
    }
}



 

You will also have to open ExternalstorageFactory.java and adapt it slightly:

 

package com.rho.externalstorage;

import com.rhomobile.rhodes.api.RhoApiFactory;

public class ExternalstorageFactory
        extends RhoApiFactory< Externalstorage, ExternalstorageSingleton>
        implements IExternalstorageFactory {

    @Override
    protected ExternalstorageSingleton createSingleton() {
        return new ExternalstorageSingleton();
    }

    @Override
    protected Externalstorage createApiObject(String id) {
        return new Externalstorage(id);
    }
}



 

 

That's all from the native side - back to the comfortable world of RhoMobile: open your build.yml and add your new extension like any other:

 

extensions: ["externalstorage"]



 

You are now ready to see the extension in action. Open an erb view file and add this simple code:

 

    <div>The path to the SD card is: <%= Rho::Externalstorage.getSDPath %></div>


    <div>
      <a onclick="alert('The path to the SD card is: '+Rho.Externalstorage.getSDPath())">Show me the path to the SD Card</a>
    </div>



 

And voila! your first native extension in action right before your eyes, accessible from Ruby and Javascript and perfectly integrated with the rest of the platform.

 

The next time you need something that RhoMobile does not offer out of the box, remember that implementing it yourself can be easier than you think.