Building a Cordova plugin for your native SDK

The HyperTrack SDK collects data about user movement viz. location, activity and device health. Developers use this data to build location tracking experiences, alerts in case of delays on the way and mileage tracking for billing and reimbursements. The SDK is built to collect and transmit a battery-efficient stream of movement data. The SDKs for Android and iOS are fully native so they can access the core platform-level location, activity and network APIs.

Our SDK users build their apps on a variety of native and non-native platforms. The need for better movement features is not limited to native Android and iOS apps — apps that are built on React Native, Cordova, Ionic and Xamarin are also starved for better movement APIs.

In the quest to enable developers on these platforms, we have struggled with setting up build environments juggling other native dependencies and then distributing these bindings. In this post, we share our learnings from building a Cordova plugin for our SDK.

If you are building a Cordova plugin the official documentation has a useful guide to start. The steps below are intended to cover some crucial steps and help avoid potential pitfalls.

Step 1: Create plugin boilerplate

Plugman is a handy tool that sets up the plugin project with support for iOS and Android. In addition you can also use it to create a package.json file to distribute the plugin via npm.

Install plugman

$ npm install -g plugman

Create a plugin

$ plugman create --name HyperTrackWrapper --plugin_id cordova-plugin-hypertrack --plugin_version 0.1.0

Setup platforms

$ cd HyperTrackWrapper
$ plugman platform add --platform_name ios
$ plugman platform add --platform_name android

Create a package.json file for npm

$ plugman createpackagejson .

After you create the plugin and add the platforms, the directory structure of the plugin will look like this. Once the directory structure is in place, it was useful to implement the Echo example in the Cordova docs.

HyperTrackWrapper
  |-- src
  |   |-- android
  |   |   |--HyperTrackWrapper.java
  |   |
  |   |-- ios
  |   |   |--HyperTrackWrapper.m
  |
  |-- www
  |   |-- HyperTrackWrapper.js
  |
  |-- plugin.xml
  |-- package.json

Step 2: Setup Android dependencies

Cordova plugins support native dependencies through Gradle. For the HyperTrack SDK wrapper, we created a gradle file with references to the SDK and relevant Google Play Services packages.

This gradle file was saved as hypertrack-gradle.xml in the src/android directory of the plugin.

dependencies {
  compile('io.hypertrack:transmitter:1.4.20:release@aar') {
    transitive = true;
  }
  compile 'com.google.android.gms:play-services-gcm:9.6.1'
  compile 'com.google.android.gms:play-services-location:9.6.1'
}

For the gradle configuration to be picked up while compiling, the plugin.xml file needs to have a reference to the gradle file. This is done with the framework tag with the gradleReference type attribute.

<platform name="android">
  ...
  <framework custom="true" src="src/android/hypertrack-sdk.gradle" type="gradleReference" />
</platform>

Step 3: Setup iOS dependencies

The HyperTrack iOS SDKs are distributed using CocoaPods. With the recent Cordova 6.4.0 release, CocoaPods support was added to the Cordova iOS platform.

To add the native iOS dependencies, we added the following framework tags to the plugin.xml file. The first one imports the SDK from CocoaPods and the second one imports the sqlite dynamic library into the plugin. While defining CocoaPods dependencies, don’t forget to mention the version using the spec attribute or else the build will fail!

<platform name="ios">
   ...
   <framework src="HTTransmitter" type="podspec" spec="~> 0.12.6" />
   <framework src="libsqlite3.dylib" />
</platform>

The framework tag documentation is a useful reference link to have while adding native dependencies for Cordova platforms.

Step 4: Configure APIs

In this last step we define some native API wrappers. The www/HyperTrackWrapper.js file defines the methods to be exported to the Cordova application. In our native SDKs, the startTrip method triggers the SDK to start trip.

hypertrack.startTrip = function(driverID, taskIDs, success, error) {
    exec(success, error, "HyperTrack", "startTrip", [driverID, taskIDs]);
};

The js method is then individually defined for iOS and Android. Once the Cordova method is implemented, the respective native methods are invoked at runtime. These methods talk to the native SDKs — bringing native capability to the Cordova application.

The startTrip method is defined for iOS in the ios/HyperTrackWrapper.m file. Notice how it calls the native SDK method internally to virtually translate the call from js to objective-C.

- (void)startTrip:(CDVInvokedUrlCommand*)command{
    NSString* driverID = [command.arguments objectAtIndex:0];
    NSMutableArray* taskIDs = [command.arguments objectAtIndex:1];       
    __block CDVPluginResult* pluginResult = nil;
    
    HTTripParams* tripParams = [[HTTripParams alloc] init];    
    tripParams.driverID = driverID;
    tripParams.taskIDs = taskIDs;
    [[HTTransmitterClient sharedClient] startTripWithTripParams:tripParams completion:^(HTResponse <HTTrip *> * _Nullable response, NSError * _Nullable error) {
        if (error) {
            // Call failure callback
            NSDictionary *failure = @{@"error" : error.localizedDescription};
            pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:failure];
        } else {
            // If there is no error, use the tripID received in the callback in your app.
            NSDictionary *success = @{@"trip" : response.result.dictionaryValue.jsonString};
            pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:success];
        }
         [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
    }];
}

The Android method is defined in the android/HyperTrackWrapper.java file. Like the iOS method it calls the native SDK methods to translate the call.

private void startTrip(String driverID, ArrayList<String> taskIDs, final CallbackContext callbackContext) {
    Context context = this.cordova.getActivity().getApplicationContext();
    HTTransmitterService transmitterService = HTTransmitterService.getInstance(context);
    HTTripParams htTripParams = new    
    HTTripParamsBuilder().setDriverID(driverID)
            .setTaskIDs(taskIDs)
            .createHTTripParams();
    transmitterService.startTrip(htTripParams, new HTTripStatusCallback() {
        @Override
        public void onSuccess(boolean isOffline, HTTrip htTrip) {
            // call success callback
            callbackContext.success(result);
        }
        @Override
        public void onError(Exception e) {
            // call error callback
            callbackContext.error(result);
        }
    });
}

The HyperTrack plugin for Cordova enables powerful location features in Cordova and Ionic apps with a few API calls. Sign up to track movement of your app users within minutes and get started!