Cordova: How to write native plugins that can repeatedly invoke a Javascript callback?
I think, you can use a pluginResult
with the keepCallback
property set to true
.
PluginResult result = new PluginResult(PluginResult.Status.OK, "YOUR_MESSAGE");
// PluginResult result = new PluginResult(PluginResult.Status.ERROR, "YOUR_ERROR_MESSAGE");
result.setKeepCallback(true);
callbackContext.sendPluginResult(result);
You should be able to invoke the callback several times this way.
n.abing
Updated on June 05, 2022Comments
-
n.abing almost 2 years
When developing Cordova plugins, all of the tutorials I have found go something like this:
File: AwesomePlugin.js
var AwesomePlugin = { kungfuGripAction = function(target, successCallback, failureCallback) { return cordova.exec( successCallback, failureCallback, 'AwesomePluginClass', 'kungfuGripAction', [target] ); } }; module.exports = AwesomePlugin;
File: AwesomePluginClass.java
@Override public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { if (ACTION_KUNGFU_GRIP.equals(action)) { JSONObject target = args.getJSONObject(0); if (gripTarget(target)) { callbackContext.success("Target successfully gripped."); return true; } else { callbackContext.error("Could not grip target."); return false; } } Log.d(LOG_TAG, "INVALID ACTION! " + action); callbackContext.error("Invalid action: " + action); return false; }
File: clientCode.js
AwesomePlugin.kungfuGripAction(cobraEnemy, function(ok) { }, function(err) { });
In the above code, the callbacks can only be called once and are then disposed. If you attempt to call the
.success()
or.error()
method of the callback context object, it will not work and you will get a log message:Attempted to send a second callback for ID: AwesomePlugin2982699494<BR>W/CordovaPlugin(976) Result was: "Target successfully gripped."
It seems like it is not possible to write a method with a callback that can be called repeatedly seeing as
.success()
and.error()
are the only documented ways to invoke a callback from within native plugin code. While this is mostly what we want, there are times when we want to have the plugin execute a callback repeatedly. For example:AwesomePlugin.kungfuGripAction(cobraEnemy, function(ok) { // After successful grip, punch repeatedly and update life meter. AwesomePlugin.punchRepeatedly(cobraEnemy, function(hits) { updateLifeMeter(cobraEnemy, hits); }, function(err) { }); }, function(err) { });
AwesomePlugin.punchRepeatedly()
above will execute repeatedly (maybe in a separate thread) and callfunction(hits)
with each successful execution. If implemented in the de-facto way (using single-use callbacks), you have to either use a loop (which is bad as it is non-async) or tail-callAwesomePlugin.punchRepeatedly()
in the callback (error-prone).What would be the correct way to implement
punchRepeatedly()
in native code so that it is able register the callback once and then execute it repeatedly? -
n.abing over 9 yearsThanks, this works. The
PluginResult.setKeepCallback(true)
was the key to this. -
n.abing about 8 yearsYou should generally avoid modifying framework code directly. When you upgrade your Cordova framework your changes are lost. You should also not need to change the message in a plugin result as you already set that when you create a new instance and then send it to the callback. The only time you might want to do this is if you are re-using a single instance of
PluginResult
in your plugin, which is also a bad idea. -
lwiu about 8 yearsYou are right.But I mean to reuse the single instance of Pluginresult by setting setKeepCallback (true),so what's the better way to send a different message each time?In swift language I can use an extension
-
n.abing about 8 yearsWhat do you mean? You don't have to reuse a single instance of
PluginResult
. You can instantiate a newPluginResult
each time you need to send back a result to the callback and use a different message if you want. Usingresult.keepCallback(true)
only tellscallbackContext.sendPluginResult()
to not mark the callback as "used" so that you can reuse thecallbackContext
in sending results again. -
lwiu almost 8 yearsI originally tried to do as you said,but it would only receive the first callback.
I/chromium: [INFO:CONSOLE(268)] "First success", source: file:///android_asset/www/build/js/app.bundle.js (268) W/IInputConnectionWrapper: showStatusIcon on inactive InputConnection D/CordovaInterfaceImpl: Sending activity result to plugin W/CordovaPlugin: Attempted to send a second callback for ID: Bluetooth435896053 Result was: "Third success"
-
n.abing almost 8 yearsIt's hard to tell what you are doing wrong without any context or sample of your code. But that should not happen if you create a new instance of the
PluginResult
each time you send it viaCallbackContext.sendPluginResult()
. You probably did not callPluginResult.keepCallback(true)
. As I said above,keepCallback(true)
only instructssendPluginResult()
to not mark the callback as "used" so that you can keep reusing it. You can look at the code forsendPluginResults()
here: github.com/apache/cordova-android/blob/master/framework/src/org/… -
lwiu almost 8 yearsI got what you said !Did you mean each instance of Pluginresult should setKeepCallback(true)?!Thank a lot 👍
-
n.abing almost 8 yearsYes, after you create the new instance of
PluginResult
you should use setKeepCallback(true) before using it insendPluginResult()
, otherwise you will get the error you described above. -
tshm001 almost 6 years@n.abing doesn't this trigger the success callback multiple times by passing status.ok each time you're sending a new callback?
-
n.abing almost 6 years@TimothyShamilov no it does not do that. It's been a long time since I've written anything in Cordova but IIRC
result.setKeepCallback(true)
only ensures that the callback function is not destroyed when theresult
is destroyed. When in doubt, use the source :)