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());
    });                
}

25 comments:

  1. Make sure that you've included the PhoneGap libraries (e.g. download PhoneGap from PhoneGap.com, add the Android phonegap.jar to your project's lib folder, configure your build path, add the phonegap.js file to your assets/www folder). There's more information here: http://www.practicaldeveloper.net/2011/05/starting-project-with-phonegap-in.html

    ReplyDelete
  2. I added phonegap-1.0.0.jar in lib folder but show the alertbox in emulater Error Initiaizing phonegap class not found

    ReplyDelete
  3. Hey Dan,

    If you add:

    PhoneGap.addConstructor(function() {
    PhoneGap.addPlugin("demo", new DemoPlugin());
    });

    You can then replace:

    var plugin = new DemoPlugin();
    plugin.sendSMS(...

    with:

    window.plugins.demo.sendSMS(....

    and that's they way we recommend calling plugins.

    Keep up the great work!

    Simon

    ReplyDelete
  4. @kumar - make sure that you follow the "Getting Started with Phonegap" guide and that you can show the basic hello world before you jump in to the plugin side. It definitely sounds like you don't have PhoneGap configured in your project.

    ReplyDelete
  5. @Simon - thanks for the feedback, I'll add it to the post!

    ReplyDelete
  6. I'm trying to use your demo plugin however when I click on Send SMS nothing happens, I really need an SMS Plugin for android for my app to work (I'll cover iOS later). Any idea what could be going wrong? Is this plugin compatible with pg 1.1.0?

    ReplyDelete
  7. Javascript typos are the most common reason I've seen for "nothing happens". Run your code through jslint to make sure it looks ok. What is the output of logcat? Stepping through the code in debug mode, is your plugin called at all?

    ReplyDelete
  8. Is there anyway you can provide a zip archive of your test project? I'm still having issues integrating it, and I have a project I would really love to use this on. Thanks!

    ReplyDelete
  9. And yes I checked for typos, copied and pasted everything I could.. I'm not sure if its an issue with me not following instructions, or that I'm new to PhoneGap plugins and this is where my inexperience is biting me.

    ReplyDelete
  10. I have a zip file, just need to find a place to put it.

    ReplyDelete
  11. Well I would offer to communicate with you directly and arrange that, but I could not find a way on this blog to do that, and I'm sure other users could benefit from an example project as well. Perhaps putting your plugin directly on github would be best, or at least temporarily on any of the free file sharing sites.

    ReplyDelete
  12. Here's a link to a zip of the project I used for this blog article.

    http://dl.dropbox.com/u/5617479/PluginExample.zip

    ReplyDelete
  13. Dan, that is awesome it was an issue on my side, you should really post this an official PhoneGap plugin. I have this confirmed working with the latest jQuery and PhoneGap 1.1.0!

    ReplyDelete
  14. Guessing that Larsinger found his issue, but to respond to his original post: class not found implies a disconnect between the plugins.xml file and the Java class name

    ReplyDelete
  15. When I try to use this I get:
    The import com.phonegap.api.PluginResult.Status cannot be resolved

    Looks identical to this:
    https://github.com/phonegap/phonegap-plugins/issues/445

    ReplyDelete
  16. I believe the plugin structure has changed in Phonegap 1.5. What version are you using.

    ReplyDelete
  17. That's your problem. It should work with PhoneGap 1.0 to 1.4. The changes to the plugin API are probably minor, but I haven't yet had a chance to look at them.

    ReplyDelete
  18. Thank you for help. Do you know from where I can get PhoneGap 1.0 or any other version working with SmsPlugin?

    ReplyDelete
  19. Look at the tags on github.com for earlier versions. https://github.com/phonegap/phonegap/tree/1.4.1/

    ReplyDelete
  20. Thank you. I will try with older phonegap version.

    Meanwhile I've got a message from someone on GitHub:

    "The package name change in 1.5 is causing the problem.
    Do a:
    import org.apache.cordova.api.*;
    and change all references of ctx into ctx.getContext()"

    I did what he said, but still something's wrong - there are still few errors:
    "Plugin cannot be resolved to a type"
    "Status cannot be resolved to a variable"
    "PluginResult cannot be resolved to a type"
    "ctx cannot be resolved or is not a field"

    Probably these are quite trivial problems to solve but I am fresh in that matter...

    ReplyDelete
  21. I posted an updated version as a new blog post. There's only 2 changes necessary to the plugin source.

    ReplyDelete
  22. hey dan. nice article but when I click on Send SMS nothing happens. and i have checked the typos and every thing.can u elaborate the reason further pls.

    ReplyDelete
  23. I have problem when i used in my mobile device it alert message that sms send successfully but actully end device not get any message please help me!!!!!

    ReplyDelete