Using Cordova Plugins in React Native (Android)

A tool use Cordova plugins with React Native - link
Apache Cordova has a pretty vibrant ecosystem of plugins. These plugins usually comprise of 2 parts - a native component that interacts with the device APIs and a JavaScript layer that makes these APIs simpler. Creating Native Modules with React Native also has a similar model of operation.
Given that more than 1000 Cordova plugins exist today to access device capabilities from JavaScript, it only seems logical to be able to reuse these for any ReactNative projects. 

This blog is about the internals on how Cordova plugins can be used with ReactNative. The scenario only works on Android for now, as shown in the video below. 

[Link to Video]
Update: Note that the demo adds plugins using node_modules/rncp/cli/cli.js. Now that the module is on npm, you could simply use node_modules/.bin/cordova-plugin add pluginname.

You can use this sample application to test the APIs.

Step 0: Installing CordovaPlugin Adapter

Using native modules in a ReactNative project usually means adding the dependencies in build.gradle, adding the sub project, etc. Instead of doing this for every single cordova plugin, I was able to leverage Cordova's node based plugin manager (called plugman). Hence, you just add the dependencies once to set up the "Cordova Adapter ReactNative" (lets call it CARN).
CARN is on npm and you can install it in the locally in the ReactNative project.

Step1 : Installing the plugins

With CARN installed, you can use it's command line interface to add plugins.
  1. Add plugins using $ node_modules/.bin/cordova-plugin add cordova-plugin-name. This command simply relies on plugman to download the plugin from npm.
  2. Once the plugin is downloaded, plugman looks at the plugin's plugin.xml file to determine any other plugins that need to be installed as dependencies - all these are stored in a assets/xml/config.xml to be used later. 
  3. Plugman then copies over the javascript, java file and JARs to the appropriate locations. These locations are defined in CARN's build.gradle. 
  4. Plugman also looks at plugin.xml to change config files like AndroidManifest.xml to add any activities or permissions that are needed for the plugin to work well. 
  5. Finally CARN combines all the Javascript files from all plugins using Cordova's module builder system (which is similar to browserify). 
  6. This is later "required" by the ReactNative's to start using the plugins. 

Step 2: Using the Plugin

  1. All Cordova plugins are available when CARN's javascript is "required" in ReactNative JavaScript. For example, the API that cordova-plugin-device exposes is typically called using require('react-native-cordova-plugin').navigator.device.getInfo(successCallback, failCallback).
  2. All the Javascript APIs that Cordova plugins expose eventually call a cordova.exec(service, action, callbackID) method, which in turns calls the exec method exposed on the Java side using the WebView.addJavascript Interface. Unlike Cordova, Reactnative does not have a webview. Hence we hijack this method and use ReactNative's Native Modules to expose a corresponding "exec" method on the Java side.
  3. Once the "exec" method on Java is invoked, it starts Cordova's Java PluginManager. This PluginManager consults the assets/xml/config.xml to look at the ClassName for the service (which is the plugin name) and calls the corresponding action on that classname provided by the plugin. 
  4. All results are asynchronous, so the plugin continues working on the method call. A plugin may have to invoke another activity like in the case of a ContactPicker or Camera using startActivityForResult.
  5. The plugin is finally done with its work. In case another activity (like Contact Picker) was  invoked, a contact is picked and our ReactNative's MainActivity is started again with the result from the previous activity. This result passed to the plugin's onActivityResult callback
  6. Now that we have the result, the plugin manager calls the WebView to deliver the result back. In our case, we instead have a MockWebView for ReactNative that uses the RTCEventEmitter to deliver the result back.
  7. We already have a listener on the JavaScript side that looks at the result that also contains a callbackID. We consult our list of callbackIDs, and call the successCallback or failCallback. 
The code itself is pretty straightforward and mostly reuses Cordova's plugman and PluginManager for most of the work.  The github repository has all this code, and the best place to start looking would be the CordovaPluginAdapter for Java side of things, and index.js for Javascript side of things.

Next Steps

The steps described above are Android specific, and I am assuming that we would have a very similar flow for iOS too. Including almost all of Cordova for Runtime may not be not most efficient, and I am working on trimming down the dependencies to make this even faster.
Would love to hear any feedback you may have and any suggestions or contributions for Android, or the iOS implementation.

[1] Note that the demo is on Windows. ReactNative still has issues to work with Windows, and I had to modify some files in ReactNative to get it to work on Windows. 
[2] If  you are using Windows, I would recommend using this Android Emulator - it works with HyperV on and is super fast !!