Wednesday, September 14, 2011

Creating a Plugin with PhoneGap 1.0

PhoneGap allows developers to access many native phone features from Javascript/HTML5 pages running in a local web browser.  Unfortunately, not every feature has been implemented.  So what do you do when you need something that's not provided by PhoneGap?  You roll up your sleeves and write a plugin.

This guide is specifically for PhoneGap 1.0.  Earlier versions used a slightly different model.

One native feature that would be convenient to have available is the ability to send text messages from our app.

Let's start with a vanilla page

<!DOCTYPE html>
<html>
    <head>  
        <script src="jquery-1.6.min.js" type= "text/javascript">
        </script>
        <script src="jquery.mobile-1.0b3.min.js" type="text/javascript">
        </script>
        <script src="phonegap-1.0.0.js" type="text/javascript">
        </script>
        <script type="text/javascript">             
        function onDeviceReady () {
   $('#send').bind('click', function () {
    alert('This will send a text message'); 
   });                
        }
        document.addEventListener("deviceready", onDeviceReady, false);
        </script>       
        <link rel="stylesheet" type="text/css" href= "jquery.mobile-1.0b3.min.css">
        <meta name="viewport" content= "width=device-width; height=device-height; user-scalable=no">             
 </head>
 <body>
  <div id="startPage" data-role="page" data-theme="a">
   <div data-role="header">
    <h1>SMS Demo</h1>
   </div>
   <div data-role="content">
    <label for="phone">Recipient Number:</label>
    <input type="tel" id="phone" name="phone" placeholder="SMS Number"/>
    <label for="message">Message:</label>
    <textarea id="message" name="message">
    </textarea>  
    <a href="#" id="send" data-role="button">Send</a>  
   </div>
  </div>
 </body>
</html>


The bridge between our javascript and PhoneGap is a custom javascript object

var DemoPlugin = function () {
 
};

DemoPlugin.prototype.sendSMS = function (successCallback, failureCallback, phone, message) { 
 return PhoneGap.exec(successCallback, failureCallback, 'DemoPlugin', "SendSMS", [phone, message]);
}

Add this to your HTML with

<Script src="demoplugin.js" type="text/javascript" />


In order to create our plugin, we'll create a new Plugin class in Java, and configure it in the plugins.xml file.  Right click on your package name in Eclipse and select New->Class.  Inherit from PhoneGap's Plugin class and implement inherited methods..

The general pattern for a PhoneGap plugin is to define an Action String that will be passed to the plugin describing what the user wants to do.  The arguments to the function are passed in a JSONArray.  After processing the plugin exec call, the function returns a PluginResult which can indicate success or failure (and even return variables.  This result will call either the successCallback or the failureCallback supplied by your javascript.

package net.practicaldeveloper.demo;

import org.json.JSONArray;
import org.json.JSONException;

import android.app.PendingIntent;
import android.content.Intent;
import android.telephony.SmsManager;

import com.phonegap.api.Plugin;
import com.phonegap.api.PluginResult;
import com.phonegap.api.PluginResult.Status;

public class DemoPlugin extends Plugin {
 public final String ACTION_SEND_SMS = "SendSMS";
 
 @Override
 public PluginResult execute(String action, JSONArray arg1, String callbackId) {
  PluginResult result = new PluginResult(Status.INVALID_ACTION);
  
  if (action.equals(ACTION_SEND_SMS)) {
   try {
    String phoneNumber = arg1.getString(0);
    String message = arg1.getString(1);
    sendSMS(phoneNumber, message);
    result = new PluginResult(Status.OK);
   }
   catch (JSONException ex) {
    result = new PluginResult(Status.ERROR, ex.getMessage());
   }   
  }
  
  return result;
 }

 private void sendSMS(String phoneNumber, String message) {
  SmsManager manager = SmsManager.getDefault();
  
        PendingIntent sentIntent = PendingIntent.getActivity(this.ctx, 0, new Intent(), 0);  
  
  manager.sendTextMessage(phoneNumber, null, message, sentIntent, null);
 }

}
In order to register this plugin with PhoneGap, add a single line to the res/plugins.xml file:
<plugin name="DemoPlugin" value="net.practicaldeveloper.demo.DemoPlugin"/>
Don't forget to add the permissions your app needs to the manifest
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="net.practicaldeveloper.demo"
      android:versionCode="1"
      android:versionName="1.0">
    <uses-sdk android:minSdkVersion="7" />

 <uses-permission android:name="android.permission.SEND_SMS"/>
 <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
 <uses-permission android:name="android.permission.INTERNET" />
 
    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".PluginExampleActivity"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

    </application>
</manifest>

Finally, you can modify your javascript to call the plugin asynchronously.
function onDeviceReady () {
    /*
     * Updated based on a comment from Simon 
     * Mac Donald.  This is the recommended
     * syntax for integrating plugins 
     */
    PhoneGap.addConstructor(function() {
        PhoneGap.addPlugin("demo", new DemoPlugin());
    });    

    $('#send').bind('click', function () {        
        //var plugin = new DemoPlugin();
        window.plugins.demo.sendSMS(function () { 
           alert('Message sent successfully'); 
        },
        function (e) {
            alert('Message Failed:' + e);
        },
        $('#phone').val(), 
        $('#message').val());
    });                
}