Jimmy Jarvis
Jimmy Jarvis

Reputation: 21

PhoneGap / Cordova Callback Bug - RestKit Completion Functions Not Executing in PhoneGap

PhoneGap callbacks do not seem to work if called from within a RestKit callback. We have validated our PhoneGap callback logic works fine by removing the RestKit call (plus all of our other PhoneGap plugin callbacks work fine). In the code below, the loader.onDidLoadResponse is executed upon completion of the RestKit call, however, even through the PhoneGap / Cordova callback line executes, the corresponding completion routine in javascript never executes. It's as if the callback vanishes. Are we doing something wrong with the context or how we've written the RestKit or PhoneGap asynchronous logic?

Any help would be greatly appreciated.

-(void)GetRestJson:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options
{
    NSString* callbackID = [arguments pop];
    NSLog(@"%@",callbackID);
    NSString* url = [arguments objectAtIndex:0];
    NSString* sublocation = [arguments objectAtIndex:1];
    NSDictionary * args = [NSDictionary dictionaryWithObjectsAndKeys:
                           callbackID, @"callBackID",
                           self, @"thisObject",                           
                           nil];
    RKClient* client = [RKClient clientWithBaseURL:[RKURL URLWithString:url]];

// <---- RESTKIT CALL MADE (WORKS)
    [client get:sublocation usingBlock:^(RKRequest* loader)
     {
         loader.onDidLoadResponse = ^(RKResponse *response)     // <---- RESTKIT COMPLETION CALLED (WORKS)
         {
             NSString* result = [response bodyAsString];
             NSString * callbackID = [args objectForKey:@"callBackID"];
             id callingObject = [args objectForKey:@"thisObject"];

             CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK
                                                               messageAsString: [result stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
             [callingObject writeJavascript: [pluginResult toSuccessCallbackString:callbackID]];  // <---- PHONEGAP CALLBACK MADE (BROKEN --- DOESN'T ARRIVE BACK ON JS CALLBACK)

         };

         loader.onDidFailLoadWithError = ^(NSError *error)
         {
             NSString* result = [error description];
             NSLog(@"Loaded payload: %@", result);
             NSString * callbackID = [args objectForKey:@"callBackID"];
             id callingObject = [args objectForKey:@"thisObject"];
             CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK
                                                               messageAsString: [result stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
             [callingObject writeJavascript: [pluginResult toErrorCallbackString:callbackID]];
         };
     }];
}

Upvotes: 1

Views: 1389

Answers (1)

Jimmy Jarvis
Jimmy Jarvis

Reputation: 21

Two obscure bugs prevented RestKit completion functions from executing:

PROBLEM #1 - PhoneGap 2.0.0 / Cordova 2.0.0 has a bug in the asynchronous callback logic

PhoneGap callback logic has a bug where it can trigger the callback function of an unrelated call from a different context. When a call from Javascript is made, PhoneGap generates a new callbackId, adds it to the cordova.callbacks array, and passes it along to the native code. Some time later, when the native code completes the request, it injects javascript to call the callbackSuccess or callbackError function. The respective callback handler looks up the callback attributes from the cordova.callbacks array and executes the original caller's completion function. The problem is each page, or reload of a page, resets the callbackId counter to zero and new calls end up reusing the same callbackId, even though the native code haS not yet completed the prior request. This can occur upon a new page load or reload upon an empty href in an anchor tag. A call to native code from page1.html could trigger the completion routine on page2.html because the native code has no context of the change on the other side of the bridge.

SOLUTION: Replace the incrementing callbackId with a unique identifier to insure only the caller's completion logic will be called. I have done this with a Pseudo-GUID generator. Fix outlined below:

REPLACE THIS LINE in both cordova.2.0.0.js and cordova.ios.js

callbackId = service + cordova.callbackId++;    // BUG: incrementing callbackId's can call unrelated callback logic

WITH THIS LINE

callbackId = service + ":" + 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {var r = Math.random()*16|0,v=c=='x'?r:r&0x3|0x8;return v.toString(16);}); // Generate unique callbackID with Pseudo-GUID

PROBLEM #2 - Page reload due to anchor with empty href

The calling page uses an anchor tag with an empty href="" which causes the current page to reload and $(document).ready to fire again. The reload resets the cordova.callbackId counter and cordova.callbacks array. The newly fired .ready function makes new calls to the native code and reuses the same callbackId's which are already in use and still pending from the prior page instance in the native code. When the RestKit call completes, PhoneGap/Cordova fires a callbackId to a now duplicate entry to unrelated logic.

SOLUTION: Fixing the callbackId logic as outlined above prevents unrelated callback logic from executing. The second part of the solution is to remove the href from the anchor tag or just use an onclick in a div element to prevent page reload or navigating to a new page which reuses callbackIds.

Upvotes: 1

Related Questions