Friday, September 16, 2011

Phonegap plugins - Part 1 Developing simple plugins

What is a phonegap plugin?


A phonegap plugin is a javascript handle to native device code. So on the iPhone, a plugin is a javascript handle to a function or even class that is written in objective-c. From javascript you simply make call the native code with a command that looks like:
Phonegap.exec("myObjectiveCFunction", myArgument1, myArgument2, etc);

Background


I have been writing a HTML5 game for iPhone for a good while now (too long a while). The game is at last near completion. It's called Bubble Electric and here's a landing page.

The game has been out for a while now on webOS, as Bubble Puzzle, and is approaching a respectable 100k downloads. Unfortunately alot of these downloads are for the free version. Whilst I have not made my fortune I can at least point to 4/5 user rating I get for the game as a soft consolation.

I have written the majority of the game in javascript and worked hard to get webkit transitions working well. It's been difficult but I think I've done a good job (of course I must say this!).

It has to be said that the iPhone is well ahead of the competition when it comes to webkit transitions. webOS is not bad but seems to favour canvas and Android looks to be good on higher end phones like the Samsung Galaxy but sucks big time on my Android: an old HTC Hero.

But I digress. Let's get back to phonegap plugins. You may ask why use plugins when the iphone does such a good job of rendering webkit. Well everything is not about rendering in a game. Sound is important, connecting your game to social marketing tools is important (like Openfeint, Flurry), etc.

In the case of sound, Phonegap by default calls to objective-c to play audio. It does not go near HTML5 audio tags. Why? Because Apple has done a great job with audio in obj-c. Playing sounds with native obj-c does not disrupt rendering performance but HTML5 audio tags will. webOS has failed to tackle the the audio issue in a meaningful way and is one reason why webOS did not hit the ground running. I tested HTML5 audio tags in the webOS version of the game and playability of the game was ruined.

In the case of services like Openfeint, the detailed API has been written in objective-c so there's simply no other way to use it. Yes they have released a RESTful api but it's very much a beta and lacks the power and functionality of the obj-c version.

Let's get started


So let's get to the point and hack together a simple plugin for phonegap. We're going to create an alert dialog box. This is a little bit redundant as you can call alert(message) in js anyway but it will show the basics of a plugin. This is how I started writing plugins.

1. Open up your phonegap project in Xcode and add a new file to the Plugins folder.
2. Select Objective-C class as the file type.
3. Name the file and save it.
4. You should have 2 files relating to the class you created: MyPlugIns.h and MyPlugIns.m.
5. In MyPlugIns.h type the following:

#import <Foundation/Foundation.h>  
#import "PhoneGapCommand.h" 


@interface MyPlugIns : PhoneGapCommand {
}

- (void)logAlert:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options;

6. In MyPlugins.m type the following:

@implementation MyPlugIns


- (void)logAlert:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options {

[webView stringByEvaluatingJavaScriptFromString:@"alert('This is your log alert.');"];

}

@end

7. So what are we going here? We are telling the webView to interpret the js and render it to the webView. What's the webView? The webView is the chromeless browser view that your phonegap runs in. Once you import PhonegapCommand.h the properties and methods of the webView become available for manipulation to the class you imported it to. This in itself is a handy thing to know.

8. stringByEvaluatingJavaScriptFromString is a method of the webview that simply interprets javascript in the string that follows it. In this case: @"alert('This is your log alert.')". In obj-c a string object is represented as @"string". The @ requirement is v annoying for me.

9. To call this plugin from javascript you simply write the following code in your javascript file(s) like so:

Phonegap.exec("MyPlugIns.logAlert");

10. Well that's nice you may say but what if I want to pop up a different alert message. No problem here's how to do it. Go back to the MyPlugIns.m file and edit the logAlert function as follows:

- (void)logAlert:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options {

NSString *startJS = [NSString stringWithFormat:@"%@", @"alert('"];
NSString *message = [NSString stringWithFormat:@"%@",[arguments objectAtIndex:0] ];
NSString *endJS = [NSString stringWithFormat:@"%@", @"');"];

NSString *jsString = [NSString stringWithFormat:@"%@%@%@", startJS, message, endJS];
[webView stringByEvaluatingJavaScriptFromString: jsString];

}

11. What we're doing here is basically assembling the js string we want to interpret. It's messy but it works. Note that the source data for the message string is coming via the arguments parameter. arguments is an array and so we reference it as an array to get at what it's holding using [arguments objectAtIndex:0]

Please be warned that it is easy to use NSString *myString = @"a string" but this causes problems when interpreting non string parameters you pass into the function. For example, if your message was an integer number you may want to use the following to convert it into a string:

NSString *message = [NSString stringWithFormat:@"%d",[arguments objectAtIndex:0]];

12. To call this plugin from javascript you simply write the following code in your javascript file(s) like so:

Phonegap.exec("MyPlugIns.logAlert", "This is your log alert");

To pass more than one parameter simply do the following:

Phonegap.exec("MyPlugIns.logAlert", "This is your log alert", "Hello function");

Of course you need to pick up this parameter in your obj-c function as follows:

NSString *logDetail = [NSString stringWithFormat:@"%@",[arguments objectAtIndex:1]];

13. Finally you may have noticed that the resulting alert screen always has a index.html in its head. Everyone will know this is a js app. How do I change this. Change your logAlert function as follows:

- (void)logAlert:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options {

[webView stringByEvaluatingJavaScriptFromString:@"navigator.notification.alert('This is your log alert.', 'Log alert', 'OK');"];

}

14. Hey! That function is not parameterizable. Well I will let you work that out!

Well that's it for now. In my next post I'll show you how to write more sophisticated plugins that are classes in their own right and that bring native views into play.

Hope you enjoyed. And a twitter follow, stumble, digg and/or Bubble Electric download (don't worry it's free) is always appreciated.

Best, Nige

No comments:

Post a Comment

Share