Do not fear the natives - writing native extensions in RhoMobile 4

Kutir Mobility -
6 MIN READ
4
4

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:

   

        Example extension api

        This is example of API. Implementation contain in extension.

       

           

           

       

       

            list of properties supported by instance of object

           

                simple string property

           

       

       

           

               

                    Array of Externalstorage objects

                   

               

           

           

                return string with platform

               

           

           

                return summ of two params: a+b

               

                   

                   

                   

                   

               

               

           

           

                return join of two strings: a+b

               

                   

                   

                   

                   

               

               

           

       

  

       

       

        1.0.0

       

       

   

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:

   

        Example extension api

       

       

       

       

       

       

           

               

               

               

                    Path of the SD card

               

                            

       

       

       

        1.0.0

       

       

   

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 getIDs() {

        List ids = new LinkedList();

        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:

   

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

   

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.

profile

Kutir Mobility

Please Register or Login to post a reply

4 Replies

Y Yanis Dalabiras

I followed your guide to make a native extension for sending gsm SMS silently, in android. Since this is not a capability that rhomobile provides us, can you help me what more I need to do, in addition to the above steps. Thanks in advance.

Y Yanis Dalabiras

Hi, wrote the codes as explained but it was giving me error. Tried twice already. The error is as below:

<h2>Server Error</h2>
Error: uninitialized constant Rho::ExternalstorageTrace: C:/MotorolaRhoMobileSuite4.1.1/ruby/lib/ruby/gems/1.9.1/gems/rhodes-4.1.1/lib/framework/rho/rho.rb:1243:in `const_missing'C:/MotorolaRhoMobileSuite4.1.1/ruby/lib/ruby/gems/1.9.1/gems/rhodes-4.1.1/lib/framework/rho/render.rb:161:in `getBinding'C:/MotorolaRhoMobileSuite4.1.1/ruby/lib/ruby/gems/1.9.1/gems/rhodes-4.1.1/lib/framework/rhoframework.rb:55:in `eval'C:/MotorolaRhoMobileSuite4.1.1/ruby/lib/ruby/gems/1.9.1/gems/rhodes-4.1.1/lib/framework/rhoframework.rb:55:in `eval_compiled_file'C:/MotorolaRhoMobileSuite4.1.1/ruby/lib/ruby/gems/1.9.1/gems/rhodes-4.1.1/lib/framework/rho/render.rb:90:in `inst_render_index'C:/MotorolaRhoMobileSuite4.1.1/ruby/lib/ruby/gems/1.9.1/gems/rhodes-4.1.1/lib/framework/rho/render.rb:63:in `renderfile'C:/MotorolaRhoMobileSuite4.1.1/ruby/lib/ruby/gems/1.9.1/gems/rhodes-4.1.1/lib/framework/rho/rho.rb:895:in `serve_index'

===========================================================

Can you please help on why I'm getting this?

Y Yanis Dalabiras

It appears that you are running the application on RhoSimulator. However, RhoSimulator will not load additional extensions. Try running the app either on the Android emulator or on a real Android device.

B Babatunde Oyeyemi

Hi <a href></a>,

I tried to build the app but got the following errors:

PWD: G:/RhodeWS/RhoMobileApplication/extensions/externalstorage/ext/platform/and
roid
rake arch:arm
cd G:/RhodeWS/RhoMobileApplication/extensions/externalstorage/ext/platform/andro
id
cd ../../..
rake aborted!
Don't know how to build task 'G:/RhodeWS/RhoMobileApplication/bin/tmp/externalst
orage/arm/Externalstorage.cpp.o'

Tasks: TOP =&gt; arch:arm =&gt; G:/RhodeWS/RhoMobileApplication/bin/target/android/deb
ug/extensions/externalstorage/armeabi/libexternalstorage.a
(See full trace by running task with --trace)
rake aborted!
Extension build failed: G:/RhodeWS/RhoMobileApplication/extensions/externalstora
ge/ext/platform/android

Tasks: TOP =&gt; run:android:device =&gt; device:android:debug =&gt; package:android =&gt; b
uild:android:all =&gt; build:android:rhobundle =&gt; build:android:extensions
(See full trace by running task with --trace)
***************************************************************************************************

Please help!!!