Understanding the Issue with PhoneGap Plugins on iOS
Introduction
PhoneGap is a popular framework for building hybrid mobile apps, allowing developers to use web technologies like HTML, CSS, and JavaScript to create native mobile applications. One of the key features of PhoneGap is its plugin architecture, which enables developers to extend the app’s functionality by adding custom plugins written in native code (e.g., Objective-C or Java).
In this article, we’ll delve into a common issue that developers face when using PhoneGap plugins on iOS: why some plugins might work once, but fail to function until the app is backgrounded and restarted.
Background
PhoneGap uses a callback-based approach to communicate between the JavaScript engine (e.g., Cordova) and the native code. When a plugin is invoked from JavaScript, it triggers a native method call on the plugin’s Objective-C or Java implementation. The native code then processes the request, generates a response, and sends it back to JavaScript using the callback function.
In our case, we have a PhoneGap app built with version 2.8.1, which has a custom plugin that stores app secrets, hashes given data, and returns the resulting hash as a response to JavaScript. The plugin is written in Objective-C and uses Cordova’s CDVInvokedUrlCommand class to receive the request.
The Issue
The problem arises when we try to invoke this plugin from our app while it’s already running in the foreground. Here are the symptoms:
- When we first install the app, everything works as expected.
- However, when we start the app again, the plugin fails to function until we background the app by pressing the Home button and then returning to it.
Debugging and Investigation
To understand why this is happening, let’s take a closer look at the native code implementation of our plugin. The relevant part of the hamac method looks like this:
- (void)hamac:(CDVInvokedUrlCommand*)command {
NSLog(@"HAMAC STARTED!");
CDVPluginResult* pluginResult = nil;
@try {
NSDictionary* data = [command.arguments objectAtIndex:0];
if (data != nil) {
// prepare string to be hashed
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary: @{
@"status": @YES,
@"h": [self sha1:[NSString stringWithFormat:@"%@", stringToHash]
}];
} else {
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary: @{
@"status": @NO,
@"error": @"No data!"
}];
}
} @catch (NSException* e) {
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary: @{
@"status": @NO,
@"error": @"Failed to hash data"
}];
}
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
This method takes the data object from the JavaScript request, hashes it using a custom sha1 function, and generates a response dictionary with the resulting hash.
Possible Causes
Based on our investigation, there are several possible reasons why this plugin might not be working properly:
- Native code issues: There could be some problem in the native code implementation of the plugin that’s causing it to fail. For example, maybe there’s an incorrect assumption about the
dataobject or a mistake in the hashing logic. - JavaScript request issues: The JavaScript request that invokes the plugin might not be correct, which would cause the native code to receive invalid data and fail.
- Cordova/CocoaTouch integration issues: There could be some problem with the integration between Cordova and CocoaTouch, leading to communication issues between the two frameworks.
Solution
To fix this issue, we need to identify the root cause of the problem. In our case, let’s take a closer look at the native code implementation and the JavaScript request that invokes it.
Here are some potential solutions:
- Verify native code logic: Make sure that the native code logic is correct and that there are no mistakes in the hashing or processing of the data.
- Check JavaScript request: Verify that the JavaScript request that invokes the plugin is correct and that the data being passed is valid.
- Re-enable logging: Re-enable logging for both the native code and JavaScript engines to see if we can gather any useful information about what’s going wrong.
Here are some steps you could take:
Step 1: Check native code implementation
- (void)hamac:(CDVInvokedUrlCommand*)command {
NSLog(@"HAMAC STARTED!");
CDVPluginResult* pluginResult = nil;
@try {
NSDictionary* data = [command.arguments objectAtIndex:0];
if (data != nil) {
NSLog(@"Received valid data!");
// prepare string to be hashed
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary: @{
@"status": @YES,
@"h": [self sha1:[NSString stringWithFormat:@"%@", stringToHash]
}];
} else {
NSLog(@"Received invalid data!");
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary: @{
@"status": @NO,
@"error": @"Invalid data!"
}];
}
} @catch (NSException* e) {
NSLog(@"Caught exception!");
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary: @{
@"status": @NO,
@"error": @"Failed to hash data"
}];
}
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
Step 2: Check JavaScript request
window.plugins.protocol.hamac(data, (results) => {
// verify that results is a valid array
if (Array.isArray(results)) {
// process the data
} else {
console.error("Received invalid results!");
}
});
Step 3: Re-enable logging
NSLog(@"HAMAC STARTED!");
CDVPluginResult* pluginResult = nil;
@try {
NSDictionary* data = [command.arguments objectAtIndex:0];
if (data != nil) {
// prepare string to be hashed
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary: @{
@"status": @YES,
@"h": [self sha1:[NSString stringWithFormat:@"%@", stringToHash]
}];
} else {
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary: @{
@"status": @NO,
@"error": @"No data!"
}];
}
} @catch (NSException* e) {
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary: @{
@"status": @NO,
@"error": @"Failed to hash data"
}];
}
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
Testing and Verification
Once we’ve identified the root cause of the problem, we can test and verify our solutions.
Here are some steps you could take:
Step 1: Verify native code implementation
- (void)hamac:(CDVInvokedUrlCommand*)command {
NSLog(@"HAMAC STARTED!");
CDVPluginResult* pluginResult = nil;
@try {
NSDictionary* data = [command.arguments objectAtIndex:0];
if (data != nil) {
NSLog(@"Received valid data!");
// prepare string to be hashed
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary: @{
@"status": @YES,
@"h": [self sha1:[NSString stringWithFormat:@"%@", stringToHash]
}];
} else {
NSLog(@"Received invalid data!");
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary: @{
@"status": @NO,
@"error": @"Invalid data!"
}];
}
} @catch (NSException* e) {
NSLog(@"Caught exception!");
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary: @{
@"status": @NO,
@"error": @"Failed to hash data"
}];
}
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
Step 2: Verify JavaScript request
window.plugins.protocol.hamac(data, (results) => {
if (Array.isArray(results)) {
// process the data
} else {
console.error("Received invalid results!");
}
});
Step 3: Re-enable logging
NSLog(@"HAMAC STARTED!");
CDVPluginResult* pluginResult = nil;
@try {
NSDictionary* data = [command.arguments objectAtIndex:0];
if (data != nil) {
NSLog(@"Received valid data!");
// prepare string to be hashed
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary: @{
@"status": @YES,
@"h": [self sha1:[NSString stringWithFormat:@"%@", stringToHash]
}];
} else {
NSLog(@"Received invalid data!");
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary: @{
@"status": @NO,
@"error": @"No data!"
}];
}
} @catch (NSException* e) {
NSLog(@"Caught exception!");
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary: @{
@"status": @NO,
@"error": @"Failed to hash data"
}];
}
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
Conclusion
In this article, we’ve explored a common issue that developers face when using PhoneGap plugins on iOS. We’ve identified the root cause of the problem and provided potential solutions.
By following the steps outlined in this article, you should be able to fix your plugin’s issues and get it working as expected.
Remember to always test and verify your solutions thoroughly to ensure they’re correct and effective.
Last modified on 2023-07-21