Interacting with the Android lifecycle in a Cordova Plugin

Anyone who’s attended one of my talks during the past year, knows that I love Mbientlab’s MetaWear boards. They’re great for prototyping wearables that offer rich APIs for Android and iOS. That said, when you’re prototyping a device, you may not want to have to invest a lot of time developing native Android and iOS applications. For this very reason, we decided to develop a Cordova plugin that allows you to use Cordova in order to quickly prototype MetaWear applications.

pluggycordova

We’ve done a lot of Android and iOS development, but this was, in fact, the first plugin we developed. One thing quickly became very apparent: While there’s lots of information on the Web about developing applications that use Cordova, there are not that many tutorials available about actually writing plugins. Most of these articles provide general directions on how to get started, and for many plugins, it’ll give you enough information to reach into the native APIs for the device, but in order to use the MetaWear API in Android, you need binding to a Service that exposes the API and handles communication to and from the board.

Now, if you were writing a native Android application, this step would be pretty straightforward. You’d create an activity by extending Activity, implement a Service connection and then override onCreate, onDestroy, onServiceConnected, and onServiceDisconnected. Then you’d bind the service in onCreate, get a reference to it in onServiceConnected, and unbind it in onDestroy. Your code might look something like this:

public class MainActivity extends Activity implements ServiceConnection {
    private MetaWearBleService.LocalBinder serviceBinder;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedIFinally,nstanceState);
        setContentView(R.layout.activity_main);

        // Bind the service when the activity is created
        getApplicationContext().bindService(new Intent(this, MetaWearBleService.class),
                this, Context.BIND_AUTO_CREATE);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();

        // Unbind the service when the activity is destroyed
        getApplicationContext().unbindService(this);
    }

    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        // Typecast the binder to the service's LocalBinder class
        serviceBinder = (MetaWearBleService.LocalBinder) service;
    }

    @Override
    public void onServiceDisconnected(ComponentName componentName) { }
}

Cordova plugins are a bit different. Instead of extending an activity or creating a fragment, you extend CordovaPlugin, which, in turn, doesn’t extend or implement anything else. So how do you get to the activity lifecycle? If you dig around in the documentation or source code, you’ll notice that it gives you a number of methods that you can override as part of the lifecycle. They include: onPause, onResume, onStart, onStop, onDestroy, onNewIntent, onMessage, and onActivityResult.

Armed with this, we can therefore create our plugin and make it bind and unbind a service at the correct point in the Android lifecycle. Our new code now looks something like this:

public class MWDevice extends CordovaPlugin implements ServiceConnection{
    public static final String TAG = "com.mbientlab.metawear.cordova";
    private MetaWearBleService.LocalBinder serviceBinder;
    private CallbackContext callbackContext;
    private String mwMacAddress;
    private MetaWearBoard mwBoard;
    private HashMap<String, CallbackContext> mwCallbackContexts;
    private boolean initialized = false;
   
    /**
     * Constructor.
     */

    public MWDevice() {}
    /**
     * Sets the context of the Command. This can then be used to do things like
     * get file paths associated with the Activity.
     *
     * @param cordova The context of the main Activity.
     * @param webView The CordovaWebView Cordova is running in.
     */

    public void initialize(CordovaInterface cordova, CordovaWebView webView) {
        super.initialize(cordova, webView);
        rssi = new RSSI(this);
        mwAccelerometer = new MWAccelerometer(this);
        mwCallbackContexts = new HashMap<String, CallbackContext>();
        bluetoothScanner = new BluetoothScanner(this);
        Context applicationContext = cordova.getActivity().getApplicationContext();
        applicationContext.bindService(
                                       new Intent(cordova.getActivity(),
                                                  MetaWearBleService.class),
                                       this, Context.BIND_AUTO_CREATE
                                       );
        Log.v(TAG,"Init Device");
    }

    public HashMap<String, CallbackContext> getMwCallbackContexts(){
        return mwCallbackContexts;
    }

    public MetaWearBoard getMwBoard(){
        return mwBoard;
    }

    public boolean execute(final String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
        this.callbackContext = callbackContext;
        final int duration = Toast.LENGTH_SHORT;
        // Shows a toast
        Log.v(TAG,"mwDevice received:"+ action);
        if(action.equals(INITIALIZE)){
            mwCallbackContexts.put(INITIALIZE, callbackContext);
            return true;
        }
        else{
            return false;}
    }

    @Override
    public void onDestroy(){
        cordova.getActivity().getApplicationContext().unbindService(this);
    }

    @Override
    public void onServiceConnected(ComponentName name, IBinder service){
        serviceBinder = (MetaWearBleService.LocalBinder) service;
        Log.i("MWDevice", "Service Connected");
        initialized = true;
        if(mwCallbackContexts.get(CONNECT) != null){
            connectBoard();
        }else if(mwCallbackContexts.get(SCAN_FOR_DEVICES) != null){
            bluetoothScanner.startBleScan();
        }

        if(mwCallbackContexts.get(CONNECT) == null &&
           mwCallbackContexts.get(SCAN_FOR_DEVICES) == null)
        {
            mwCallbackContexts.get(INITIALIZE).sendPluginResult(new PluginResult(PluginResult.Status.OK,
                                                                             "initialized"));
        }
    }

    @Override
    public void onServiceDisconnected(ComponentName componentName) {}

}

Now we’re able to listen for actions on execute and can call into any methods we need in the MetaWear service. If you want to see a complete implementation in action, head over here.

Once you understand how a Cordova plugin wires into the Android lifecycle, the rest will likely be fairly easy if you’re already an experienced Android developer. But, hopefully this overview makes the mechanics a bit clearer for you!

About Me: I am a Atlanta based, mobile/Android/IOS/AngularJS/Ruby developer, polyglot programmer, founder of Polyglot Programming Inc., wearable technology enthusiast and am interested in the internet of things. You will often find me purr programming and I regularly speak at conferences around the world. I am available for hire! More Posts

Follow Me:
TwitterLinkedInGoogle Plus

I am a Atlanta based, mobile/Android/IOS/AngularJS/Ruby developer, polyglot programmer, founder of Polyglot Programming Inc., wearable technology enthusiast and am interested in the internet of things. You will often find me purr programming and I regularly speak at conferences around the world. I am available for hire!

Posted in Android, Cordova, Development, Javascript, Mobile, Wearables Tagged with: , , ,

Leave a Reply

Your email address will not be published. Required fields are marked *

*