Do not fear the natives - writing native extensions in RhoMobile 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
List
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.
Kutir Mobility
4 Replies
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.
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?
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.
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 => arch:arm => 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 => run:android:device => device:android:debug => package:android => b
uild:android:all => build:android:rhobundle => build:android:extensions
(See full trace by running task with --trace)
***************************************************************************************************
Please help!!!