Cordova如何使用Plugins

Published on 2017 - 02 - 05

What are plugins?

Suppose you want to write a Cordova application to access your phone’s camera and take a picture. JavaScript, by itself, doesn’t have access to the camera. One of the great features of Cordova is that it provides a mechanism to allow JavaScript to access things it normally wouldn’t be able to. This is done via plugins.

Plugins work by providing a way for your JavaScript to communicate to the device. Figure 1 illustrates this.

Plugins are code written in native languages (Objective-C, Java, and so forth) that provide a hook that can be called by a JavaScript file loaded by your Cordova application. They allow your JavaScript code to do things it wouldn’t normally be able to do. There’s no camera.getPicture() function available to mobile browsers, but by using plugins, Cordova provides support for doing just such a thing. The Cordova version looks like so: navigator.camera.getPicture(). This only works because the plugin behind the camera support adds this functionality.

For a plugin to work across multiple platforms, multiple versions of the plugin are built. Taking the Camera plugin feature as an example, someone must create native code for Android, Windows Phone, iOS, and other platforms that can access the device camera. Once that’s done, one JavaScript API can be used so your code will work the same (or close enough) on all those platforms. Luckily there are quite a few plugins available for you to use in your Cordova projects.

Finding (and evaluating) plugins

Before we get into using plugins in your Cordova project, let’s discuss how you find them. While you can write your own plugins , most people find their plugins online. You used the command line npm to install Cordova; as mentioned previously, npm is a tool often used to install other programs. Cordova plugins are now (a very recent change) stored at www.npmjs.com so you can search for them there. At the time this article was written, there were nearly 150,000 npm packages available, but you can search for Cordova plugins using ecosystem:cordova in the search box. Or simply bookmark www.npmjs.com/browse/keyword/ecosystem:cordova. The search results are shown in figure 2.

Note that this search returns plugins and related Cordova assets. For example, cordova-app-hello-world is the source for the www folder in new Cordova projects. Providing a way to search for only plugins is something that’s currently being worked on.

For the most part, we’ll be focused on the core plugins that are maintained by the Apache Cordova project. Technically every plugin is just as usable as any other, but there is a set of plugins maintained by the Apache Cordova team that is kept up to date and tested on the platforms supported by Cordova. These plugins once were part of the core Cordova framework itself, but in version 3, every feature was converted into a plugin. This was done so that developers didn’t have to ship code for a feature they weren’t using. The core plugins are:

  • Battery Status (cordova-plugin-battery-status)
  • Camera (cordova-plugin-camera)
  • Console (cordova-plugin-console)
  • Contacts (cordova-plugin-contacts)
  • Device (cordova-plugin-device)
  • Device Motion and Orientation (cordova-plugin-device-motion and cordova-plugin-device-orientation)
  • Dialogs and Vibration (cordova-plugin-dialogs)
  • File System and FileTransfer (cordova-plugin-file and cordova-plugin-file-transfer)
  • Geolocation (cordova-plugin-geolocation)
  • Globalization (cordova-plugin-globalization)
  • InAppBrowser (cordova-plugin-inappbrowser)
  • Media and Media Capture (cordova-plugin-media and cordova-plugin-media-capture)
  • Network Information (cordova-plugin-network-information)
  • Splashscreen (cordova-plugin-splashscreen)
  • Vibration (cordova-plugin-vibration)
  • StatusBar (cordova-plugin-statusbar)
  • Whitelist (cordova-plugin-whitelist)

Managing plugins and the Cordova CLI

Now that you know what plugins do and where to find them, how do you get them into your project? The Cordova CLI provides a method to add new plugins, remove existing plugins, list what’s installed, and search against the main registry. The simplest action to take is to list the current plugins for a project. Navigate to any existing project, and simply type cordova plugin. This will output a list of plugins that for you will most likely be blank. Note that you can use the alias plugins instead of plugin. Either works just fine.

Adding a plugin is done via the cordova plugin add command. There are a couple of different options you have in terms of from where the plugin will be loaded, but for the purposes of this article you’ll use the pluginid value—that is, the cordova-plugin values you saw in the list of core plugins. The command diagram at right demonstrates this.

The result of this command can be seen in figure 3. In the figure, pay particular attention to the last line. For this project, the only installed platform was for iOS. Basically, the Cordova CLI grabbed the plugin and then prepared it for the platforms your project supports. The CLI is intelligent enough to know that if you decide to add Android, it needs to install the Camera plugin, as demonstrated in figure 4.

Removing a plugin is simply a matter of using cordova remove and the ID of the plugin you want to remove. As before, the CLI is intelligent enough to know what platforms you’ve installed for a project and will correctly remove the plugin from each, as shown in the command diagram at right.

The search option is useful if you can’t remember the name of a plugin. The CLI searches against the website (http://plugins.cordova.io) and returns the results at your command prompt. Figure 5 demonstrates the result of searching for “camera.”

Once you’ve installed a plugin, you can begin using it in your code. How you use a plugin will depend on what it does. Each plugin has its own API and documentation. Later we’ll go over a few example plugins so you can see them in action. Just remember that every plugin is unique. Be sure to carefully read its documentation to know how to correctly use it.

Plugins and the development cycle

In the past we outlined the development cycle for a typical Cordova project. How does working with plugins change this? Not much! Figure 6 demonstrates the updated process.

As you can see, we’ve simply added a new step (add plugins) to the flow. You can add plugins late in the development process (perhaps the client changes the requirements right before release, something that never happens, right?). But typically, much like with platforms, you’ll set up your plugins at the beginning of your project.

The deviceready event

First, there’s a critically important aspect of Cordova development that you need to learn. Before your application can talk to a plugin, Cordova has to set up a line of communication between your code and the device. That sounds complex, but it really isn’t. Basically, Cordova knows what it has to do on each supported platform to let plugin code access device hardware. All you have to do is wait for Cordova to finish. So how do you know when this has happened? All Cordova applications will fire an event called deviceready which can be listened to in your JavaScript code. Listening to events in JavaScript is fairly simple.

At the most simple level, the DOM method addEventListener() is what you’d use to create a listener for an event. If you use jQuery, you’d use the on API. The following listing demonstrates this.

Pretty much every single Cordova application will make use of the preceding code block. The name of the function that will be run, init in the listing, is completely up to you, but should be named something obvious. If you do any jQuery work you may be familiar with using $(document).ready as a way of delaying your code until the DOM is ready. For the most part you can mentally exchange that with the deviceready handler and use it in much the same way.

Plugin example: Dialogs

For our first example of a plugin we’ll look at the Cordova Dialogs plugin. It provides native dialogs and audio notifications to your Cordova application and is a big improvement over what JavaScript provides by default. After I demonstrate how to use the plugin and why it’s better than the default JavaScript method, you’ll use it in a sample application.

Better dialogs with the Cordova Dialogs plugin

JavaScript has long had a way to create dialog, or modal, windows. While these methods work, they tend to be avoided as they cannot be styled and can be overly obtrusive. When used in a mobile browser they can be even more obtrusive. In figure 7, a Cordova application is using the alert() method to display a message to the user.

Notice that the dialog has a title on top, index.html. This cannot be changed or modified and immediately flags your application as being a web page. The OK button cannot be changed either. Now compare the same (well, a similar) use of the Dialogs plugin’s alert() method in figure 8.

By switching to the Dialogs plugin, you have a much nicer dialog. You can customize the title as well as the button. You cannot control things like color and fonts, but you can still create a much better-looking dialog with minimal effort.

Building an application with the Dialogs plugin

Dialogs are used to alert the user about something important happening in your application. What that important event is doesn’t necessarily matter, but the idea is that you want to get the user’s attention. For this first example you’ll create an application that lets you test all of the methods of the Dialogs plugin:

  • Alerts are modal dialogs (miniwindows) that float on “top” of the application. Use this when you want to present a message to the user.
  • Prompts are also modal, but ask the user to type something in. Like alerts, they’re modal.
  • Confirmations ask the user to specifically accept or deny a particular action. Use this when you want to ensure the user really wants to perform some action while also providing the user a quick way to say no.
  • Beeps make noise. You can use this with the dialogs to really get the user’s attention.

Beeps are—obviously—not a dialog but an auditory alert; but as they’re pretty simple to use we’ll demonstrate them as well. Your final application, as shown in figure 9, will provide a set of buttons to test each aspect of the plugin.

Each of the buttons tests one aspect of the plugin and is labeled appropriately. If you extracted the code’s zip file to /Users/mary/Downloads/cordovabook, you can create a new Cordova project and seed it with the code for this example like so:

cordova create notificationdemo --copy-from=/Users/mary/Downloads/cordovabook/c4/dialog_demo

Don’t forget that before you can make use of a plugin, you need to install it. In the new project you created, run the following command:

cordova plugin add cordova-plugin-dialogs

You can also create a new project and then modify the code to match the code displayed next. Let’s begin by looking at the index.html file shown in the following listing.

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>Dialog Demo</title>
        <meta name="viewport" content="width=device-width">
        <link rel="stylesheet" type="text/css" href="css/app.css" />
    </head>
    <body>

    <button id="alertTest">Test Alert</button>
    <button id="confirmTest">Test Confirm</button>
    <button id="promptTest">Test Prompt</button>
    <button id="beepTest">Beep!!</button>

    <script src="cordova.js"></script>  
    <script src="js/app.js"></script>
    </body>
</html>

For the most part there isn’t much to talk about here. The application is rather simple—four buttons and nothing more—so the HTML part of the project is only a few lines long. This example calls out to an application-specific CSS file and a JavaScript file . You may be curious about the line that loads cordova.js . If you look at your project folder, this file doesn’t actually exist because this JavaScript file is injected by the Cordova CLI itself when you create a platform-specific build. So when you tell it to create an iOS build, for example, an iOS version of the cordova.js file will be copied along with everything else you write. If you have an Android version, the same thing happens but an Android-specific version is used instead. As a Cordova developer, you don’t have to worry about providing the file, but you must include a <script> tag loading it so your project can function correctly.

Let’s look at the CSS file shown in listing 3. Note the use of app.css and app.js in this application just because it makes it obvious that these files are application-specific and not part of some third-party library. There’s nothing special about “app” being used in the name. It’s nothing more than a convention.

body {
     margin: 20px;
}

button {
     padding: 10px;
     width: 100%;
     font-size: 1em;
}

That’s it. Only a few lines to add a margin around the body and make the buttons a bit easier to see on a mobile screen. Now let’s look at the final part of the project, the JavaScript file, shown in the following listing.

document.addEventListener("deviceready", init, false);
function init() {

    //listen for button clicks
    document.querySelector("#alertTest").addEventListener("touchend", 
        function() {

            navigator.notification.alert(
                "This is a test...", null, "Alert Test", "OK!");

    }, false);

    document.querySelector("#confirmTest").addEventListener("touchend", 
        function() {

            function youConfirmed(idx) {
                navigator.notification.alert(
                    "You clicked button "+idx+"!", null);
            }

            navigator.notification.confirm(
                "Cordova is awesome.", youConfirmed, 
                "Confirm This", ["Yes","No","Maybe"]);

    }, false);

    document.querySelector("#promptTest").addEventListener("touchend", 
        function() {

            function promptAnswer(answer) {
                navigator.notification.alert(
                    "You said: "+answer.input1, null);
            }

            navigator.notification.prompt(
                "What is your favorite food?", promptAnswer, 
                "Question", ["Ok"], "Cookies");

    }, false);

    document.querySelector("#beepTest").addEventListener("touchend", 
        function() {

            navigator.notification.beep(2);

    }, false);

}

The more important aspect of this JavaScript file is the initial event listener for deviceready. As noted earlier in the article, before you do anything with device features you must listen for this event. Therefore, the majority of the code for this application is nestled inside that function. The Dialogs plugin is documented at www.npmjs.com/package/cordova-plugin-dialogs, and the code simply demonstrates an example of each of the four methods supported by the plugin.

The alert() method

The alert() method allows for customization of the body (first argument) and title (third argument), as well as the button (fourth argument). The second argument, which is specified as null, is a callback function to run after the alert is dismissed. If you wanted your application to do anything at that point you could define it there. Figure 10 demonstrates the result.

The confirm() method

The confirm() method works similarly to the alert() method. Note though that you can actually specify multiple buttons instead of a default set of two. For the confirm() method example you want to display what the user clicked so you use the callback to alert it, as shown in figure 11.

The prompt() method

The prompt() method is also pretty similar to the last two—you provide a default prompt, a callback, the title of the dialog, a set of buttons, and an optional default. The callback is a bit more complex. It’s passed a result object that contains a key for the input (input1) and the index of the button selected (buttonIndex). For your application you alert back what the user entered, as shown in figure 12.

The beep() method

The most simple method of the plugin is beep(). You simply pass in the number of beeps to make; the sound will be based on the user’s default notification sound. Please use this method with caution and resist the urge to pass an incredibly large number to it. In case you’re curious, you can do both a beep and an alert at the same time to get the user’s attention:

navigator.notification.beep(2);
navigator.notification.alert("Wake up! ", null, "Alert Test", "OK! ");

If you click the buttons and nothing happens, most likely you forgot to include the plugin as instructed earlier.

Plugin example: Camera

Now that you’ve seen the power of a simple plugin in action, let’s kick it up a notch and talk about one of the more popular plugins, Camera. From Instagram to Facebook, users love taking pictures. The Camera plugin lets you prompt the user to take a new picture or select from the gallery. It has multiple options for where the image ends up and lets you control the size of the picture returned to your application. At the most simple level you can prompt the user to take a picture and then render that picture in your application. More advanced applications can use these pictures by uploading them to other services and manipulating them.

This demo provides buttons to prompt the user to either take a new picture or select one from the device, as shown in figure 13.

Once the user has selected an image, it will be displayed in the application and styled a bit, like figure 14.

Now let’s look at the code. Don’t forget to actually create the new project. For this application to work correctly in your emulator, or device, you’ll need to install not one but two plugins:

cordova plugin add cordova-plugin-camera
cordova plugin add cordova-plugin-dialogs

Using multiple plugins is a perfectly normal part of Cordova development and it will not be unusual for your application to use many of them.

Let’s begin by looking at the HTML for the application, shown in the following listing.

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>Basic Camera</title>
        <meta name="viewport" content="width=device-width">
        <link rel="stylesheet" type="text/css" href="css/app.css" />
    </head>
    <body>

    <button id="takePicture">Take Picture</button>
    <button id="usePicture">Use Picture</button>

    <img id="myImage">

    <script src="cordova.js"></script>  
    <script src="js/app.js"></script>
    </body>
</html>

As before, because the application is so simple, the HTML doesn’t have much to it. For the most part it comes down to the two buttons used to prompt the user to take or select a picture. The empty image will be used to display what the user takes or selects. The CSS file shown in the following listing is similar to the previous application with the addition of a new style for the image.

body {
    margin: 20px;
}

button {
    padding: 10px;
    width: 100%;
    font-size: 1em;
}

img {
    margin-top: 10px;
    -webkit-filter: sepia(100%);
    display: block;
    margin-left: auto;
    margin-right: auto;
    max-width: 100%;
    max-height: 250px;
}

The sepia filter is used to colorize the selected image. You can think of it as an Instagram filter done simple. Now review the JavaScript in the following listing.

document.addEventListener("deviceready", init, false);
function init() {

    function onSuccess(imageData) {
        console.log('success');
        var image = document.getElementById('myImage');
        image.src = imageData;
    }

    function onFail(message) {
        navigator.notification.alert(
            message, null, "Camera Failure");
    }   

    //Use from Camera
    document.querySelector("#takePicture").addEventListener("touchend", function() {
        navigator.camera.getPicture(onSuccess, onFail, { 
            quality: 50,
            sourceType: Camera.PictureSourceType.CAMERA,
            destinationType: Camera.DestinationType.FILE_URI
        });

    });

    //Use from Library
    document.querySelector("#usePicture").addEventListener("touchend", function() {
        navigator.camera.getPicture(onSuccess, onFail, { 
            quality: 50,
            sourceType: Camera.PictureSourceType.PHOTOLIBRARY,
            destinationType: Camera.DestinationType.FILE_URI
        });

    });

}

Once again the code begins with an event listener for deviceready , a consistent technique for your Cordova applications. Both buttons (shown in figures 13 and 14) make use of touch events to listen for user action. Both make use of the core Camera plugin functionality to get pictures, navigator.camera.getPicture. The only difference in the two calls are the options. The first button that’s requesting a new picture uses a source of Camera.PictureSourceType.CAMERA. This means a new picture driven by the device camera. The second button uses a source of Camera.PictureSourceType.PHOTOLIBRARY, which represents an existing picture from the device’s photo gallery.

The Camera plugin can send the picture as either Base64 data (a text representation of binary data) or from the location of the picture on the device’s filesystem. In general, using the FILE_URI destination as used here is almost always preferred because it uses less memory then the Base64 version. (This is discussed on the plugin’s documentation page.) Once the camera action is complete, the success handler is run and, because you’re passing in a file URL, you can set that to the blank image created in the HTML page. Note as well the error handler. It’s using the nice dialogs described earlier.

Plugin example: Contacts

For our final example we’ll work with the Contacts plugin, which (no surprise here) lets you add, edit, and delete contacts. That’s handy. A more typical use for contacts is to find a contact to be used in some other action. Imagine you’ve made use of the Camera plugin. Because you’ve just taken what is obviously the best picture of a cat ever, it would be even cooler if you could share that picture with a friend. The Contacts plugin would make it easy to let you find that friend and fetch their email address, or phone number, so that your friend too can bask in the awesomeness of your cat photo.

While the plugin provides a find() method for low-level searching, it has a better method of asking the user to select a contact. The plugin can ask the device to use its default Contact Picker UI that’s shared among other native applications. This provides a familiar user experience to the person working with your application. By using the navigator.contacts.pickContact() method as provided by the plugin, you can display the appropriate UI for the device’s OS, as shown in figure 15.

The application will make use of this API to simply prompt the user to select a contact. After a contact is selected, information about the contact will be displayed in the application, as shown in figure 16.

In doing so though, you’ll discover one of the issues you run into from time to time with plugins: platform quirks.

The documentation for the Contacts plugin (www.npmjs.com/package/cordova-plugin-contacts) discusses how the plugin works and points out oddities or quirks for an individual plugin. While all plugins strive to work exactly the same across their supported platforms, sometimes this is not possible.

For the Contacts plugin there’s one quirk in particular that’s going to impact the sample application. The docs say this:

displayName: Not supported on iOS, returning null unless there is no ContactName specified, in which case it returns the composite name, nickname, or “ ”, respectively.

Interesting. So if the application is going to display a contact, it will need to be prepared to handle running under iOS and not having access to that particular property.

I know you didn’t forget to create a new project and add the plugins, right? This project makes use of both the Contacts plugin (cordova-plugin-contacts) and Dialogs plugin (cordova-plugin-dialogs).

Let’s look at the code. The first template is the index.html file for the application shown in the following listing.

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>Contacts Demo</title>
        <meta name="viewport" content="width=device-width">
        <link rel="stylesheet" type="text/css" href="css/app.css" />
    </head>
    <body>

    <button id="pickContact">Pick Contact</button>
    <div id="selectedContact"></div>

    <script src="cordova.js"></script>  
    <script src="js/app.js"></script>
    </body>
</html>

As with the other examples, the HTML is rather simple. You’ve got one button you’ll use to fire off the Contacts plugin and an empty div that will be filled with details from the selected contact .

Now let’s look at the JavaScript code for the application shown in the following listing.

document.addEventListener("deviceready", init, false);
function init() {
    document.querySelector("#pickContact").addEventListener("touchend", doContactPicker, false);
}

function doContactPicker() {
    navigator.contacts.pickContact(function(contact){

        //Build a simple string to display the Contact
        var s = "";
        s += "<h2>"+getName(contact)+"</h2>";

        if(contact.emails && contact.emails.length) {
            s+= "Email: "+contact.emails[0].value+"<br/>";
        }

        if(contact.phoneNumbers && contact.phoneNumbers.length) {
            s+= "Phone: "+contact.phoneNumbers[0].value+"<br/>";
        }

        if(contact.photos && contact.photos.length) {
            s+= "<p><img src='"+contact.photos[0].value+"'></p>";
        }

        document.querySelector("#selectedContact").innerHTML=s;
    },function(err){
        navigator.notification.alert(
            err, null, "Failure");
    });
}

/*
Handles iOS not returning displayName or returning null/""
*/
function getName(c) {
    var name = c.displayName;
    if(!name || name === "") {
        if(c.name.formatted) {
            return c.name.formatted;
        }
        if(c.name.givenName && c.name.familyName) {
            return c.name.givenName +" "+c.name.familyName;
        }
        return "Nameless";
    }
    return name;
}

As in the previous examples, the code begins with a listener for deviceready. By now this should be familiar territory. The plugin API, navigator.contacts.pickContact , only takes two arguments: a success callback and a failure callback. The success callback is passed an instance of a Contact object, a basic representation of a particular contact. The documentation fully covers all the keys but for the purposes here, only a few are displayed. Note that some properties are arrays and can be iterated over. Make note of how to handle the iOS issue mentioned earlier. The getName() function will check to see if displayName() is null and, if so, try to find an appropriate value elsewhere. If all else fails it will return Nameless.

Reference