Version: 0.0.1 updated 7/1/2016
Cordova Plugins are the magic that enable our mobile web app content to access the full power of Native SDKs underneath, but through clean JavaScript APIs that work the same across all platforms we target.
Building Cordova plugins is scary for many Cordova and Ionic developers, but it doesn't have to be. This simple guide walks through the what, when, why, and how of Cordova plugin development for iOS and Android.
Before we jump into building a real plugin, some backstory is in order.
A Cordova plugin consists of some JavaScript code and Native code that communicates with each other to pass data between the web and native worlds.
A plugin is how we extend the functionality of the browser environment our Cordova apps run in to add the full power of the underlying native SDKs.
Cordova apps run primarily in a web browser which makes it easy to rapidly build apps just like we build websites. However, the browser doesn't always have all the features we need at a native level, so we need a way to write native code and communicate with it through our browser context.
Cordova comes with many plugins, and there are hundreds more in the Cordova community. However, it's very possible that a Native SDK or library we want to use lacks a corresponding Cordova plugin, and we will find we have to build our own.
The best way to start building a plugin is to clone an existing one! We've put together a simple starter template for a Cordova plugin here: https://github.com/driftyco/cordova-plugin-template
- Clone the repo:
git clone [email protected]:driftyco/cordova-plugin-template.git
cd cordova-plugin-template
- Create your app
In a different terminal, create your app:
Run ionic start
if using Ionic, or cordova create
if not.
We will be testing an developing our plugin from this app. Plugins are always built and developed from within an existing Cordova project.
- Add platforms
cordova platform add android ios
- Install and Link your Plugin
cordova plugin add --link ~/path/to/plugin
With the --link
flag, Cordova creates a symbolic link to our plugin so we can update the plugin locally and rebuild without having to copy anything.
Note: to remove the plugin, use the symbolic name of the plugin instead:
cordova plugin rm my-cordova-plugin
- Testing process
When our plugin is linked, we can make modifications to the native code in our plugin and rebuild our app immediately. If we make modifications to the JavaScript portion of our plugin, we need to reinstall the plugin (as far as I know, I am trying to find a way around this).
cordova plugin rm my-cordova-plugin
cordova plugin add --link ~/path/to/plugin
- Building our plugin
Now that the scaffolding is in place to build and test the plugin, follow the JavaScript and then platform-specific guides below to get coding.
Ideally, we should try to have our native plugin code do as little work as possible by focusing on sending data from the native layer up to the JavaScript layer and processing it there. This approach maximizes our ability to create cross-platform code, and minimizes the amount of native code to maintain. Of course, you may decide to perform more work at the native layer for performance reasons, but this is increasingly rare.
Here is a simple example of a Cordova plugin JS:
var exec = require('cordova/exec');
var PLUGIN_NAME = 'MyCordovaPlugin';
var MyCordovaPlugin = {
echo: function(phrase, cb) {
exec(cb, null, PLUGIN_NAME, 'echo', [phrase]);
}
};
module.exports = MyCordovaPlugin;
This is, largely, just standard JavaScript. The Cordova magic happens with the exec
call, which takes a few params:
exec(callback, errorCallback, pluginName, actionName, argumentArray)
callback
is called when the plugin successfully returns, and any arguments from the native plugin are passed into iterrorCallback
is called when the plugin encounters an error. We've omitted this abovepluginName
is the plugin class name on the native side.actionName
is the action we will perform on the native side.argumentArray
is an array of arguments to pass to the native side
With this call, we can send and receive data from our native plugin, and this is really 95% of what you need on the JavaScript side to interact with Cordova.
Let's focus on iOS quick. We can build Cordova plugins with Objective-C or Swift, but we will focus on Objective-C for this tutorial (here's a great Swift tutorial).
Make sure to add the ios
platform to your app: cordova platform add ios
, then open the X Code project in platforms/ios
.
Back in our plugin repo, we can edit the code in src/ios/
and as we modify the files in here, they will update in X Code and we can rebuild the app and re-run it. If we make code modifications to our plugin's www/plugin.js
file, we will need to remove the plugin and re-install it otherwise the changes won't take effect.
On iOS, Cordova commands map to functions in the implementation of our Plugin's class. For example, to handle the echo
command, we can
define the function and then implement it as such:
MyCordovaPlugin.h
#import <Cordova/CDVPlugin.h>
@interface MyCordovaPlugin : CDVPlugin {
}
// The handler for the 'echo' action
- (void)echo:(CDVInvokedUrlCommand *)command;
@end
Then, in the implementation of the plugin class:
MyCordovaPlugin.m
#import "MyCordovaPlugin.h"
#import <Cordova/CDVAvailability.h>
@implementation MyCordovaPlugin
- (void)pluginInitialize {
}
- (void)echo:(CDVInvokedUrlCommand *)command {
NSString* phrase = [command.arguments objectAtIndex:0];
NSLog(@"%@", phrase);
}
@end
echo
is a simple command and doesn't return any data back. Here's an example of a more complicated routine that returns a date string back to JavaScript:
- (void)getDate:(CDVInvokedUrlCommand *)command {
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
NSLocale *enUSPOSIXLocale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
[dateFormatter setLocale:enUSPOSIXLocale];
[dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ssZZZZZ"];
NSDate *now = [NSDate date];
NSString *iso8601String = [dateFormatter stringFromDate:now];
CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:iso8601String];
[self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
}
This assumes you've downloaded and installed the Android Studio.
Make sure to add the android
platform to your app: cordova platform add ios
. Open Android Studio, then Import a project and choose your app's platforms/android
folder.
Back in our plugin repo, we can edit the code in src/android/
and as we modify the files in here, they will update in Android Studio and we can rebuild the app and re-run it.
If we make code modifications to our plugin's www/plugin.js
file, we will need to remove the plugin and re-install it otherwise the changes won't take effect.
Unlike iOS, Android handles actions by sending them through a single method and then doing a String compare on the name of the action:
MyCordovaPlugin.java
package com.example;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.PluginResult;
import org.apache.cordova.PluginResult.Status;
import org.json.JSONObject;
import org.json.JSONArray;
import org.json.JSONException;
import android.util.Log;
import java.util.Date;
public class MyCordovaPlugin extends CordovaPlugin {
private static final String TAG = "MyCordovaPlugin";
public void initialize(CordovaInterface cordova, CordovaWebView webView) {
super.initialize(cordova, webView);
Log.d(TAG, "Initializing MyCordovaPlugin");
}
public boolean execute(String action, JSONArray args, final CallbackContext callbackContext) throws JSONException {
if(action.equals("echo")) {
String phrase = args.getString(0);
// Echo back the first argument
Log.d(TAG, phrase);
} else if(action.equals("getDate")) {
// An example of returning data back to the web layer
final PluginResult result = new PluginResult(PluginResult.Status.OK, (new Date()).toString());
callbackContext.sendPluginResult(result);
}
return true;
}
}
Cordova plugins support preference variables that can control the settings that go into the AndroidManifest (Android) and info plist (iOS). We can also specify preferences for all platforms or only for specific ones.
To add support for preferences in our plugin.xml
, use <preference
:
<preference name="URL_SCHEME" />
<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0"
id="my-plugin"
version="1.0.8">
<preference name="MY_SETTING" />
<platform name="android">
<preference name="MY_ANDROID_SETTING" default=" " />
...
</platform>
<platform name="ios">
<preference name="MY_IOS_SETTING" default=" " />
...
</plugin>
To specify variables on plugin install, add each with a --variable
flag:
cordova plugin add --link ~/path/to/plugin --variable MY_SETTING=bar --variable MY_ANDROID_SETTING=foo --variable MY_IOS_SETTING=baz
There are two options for including 3rd party libraries in Cordova plugins and/or projects that rely on them. The first is to include the code directly in the plugin itself. The second is to import the library into the app that relies on it.
Cordova plugins aren't built on their own (meaning they are built only in a real app project), so the second one is probably preferable.
Cordova plugins can get into an inconsistent state if modifications are made to things like the name of the plugin, and other lower-level plugin settings.
- Renaming a plugin
If you need to rename a plugin, first remove it from the app with cordova plugin rm my-plugin
. Rename the plugin in the plugin's plugin.xml
and reinstall it.
If Cordova complains that the plugin is already installed, or there are duplicate symbols, modify the app/plugins/ios.json
or app/plugins/android.json
and remove
any offending lines that reference the old plugin.
Some great reading to continue your Cordova plugin efforts:
You can add a reference to a gradle file in your plugin.xml file: https://cordova.apache.org/docs/en/latest/plugin_ref/spec.html#framework