I develop an ionic-cordova project. I need execute a cordova callback more than once.
Here is my Component codes:
let data =JSON.parse(JSON.stringify(this.someOptions))
this.customService.TestFunction(data).then(response => {
console.log(response);
})
.catch(ex => {
// console.log(ex)
})
}
Here is my CustomService
TestFunction(arg1: any): Promise<any> {
var temp = cordova(this, "TestFunction", { callbackStyle: "object" }, [arg1]);
return temp
}
Here is js code
exports.TestFunction = function (arg0, success, error) {
exec(success, error, 'SomeCordovaPlugin', 'TestFunction', [arg0]);
};
Here is Java codes for android
public class SomeCordovaPlugin extends CordovaPlugin {
private CallbackContext testCallbackContext;
#Override
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
if (action.equals("TestFunction")) {
this.TestFunction(args, callbackContext);
return true;
}
return false;
}
private void TestFunction(JSONArray args, CallbackContext callbackContext) {
testCallbackContext=callbackContext;
if (args != null) {
try {
for(int i=0;i\<7;i++){
PluginResult pluginResult = new PluginResult(PluginResult.Status.OK ,testCallbackContext.getCallbackId());
pluginResult.setKeepCallback(true); // keep callback
testCallbackContext.sendPluginResult(pluginResult);
}
} catch (Exception ex) {
testCallbackContext.error("An error occured:" + ex);
}
}
}
}
I want to call cordova exec more than once. pluginResult.setKeepCallback(true) is not work for me. I am using ionic 6 and cordova 11. How can I call cordova exec more than once?
If i understand your problem correctly you have a custom cordova plugin which you call from your js side.
You then want to return results from your plugin to the js world multiple times and not just once.
The problem is that the lifecycle of your plugin invocation is completed by calling sendPluginResult()
I have solved this by not using sendPluginResult() to transfer my return values to JS but calling a JS function directly via the WebView:
1. In JS set a custom function which will be called from your plugin with your result values on the object of your custom plugin like:
cordova.plugins.<pluginPackage>.<CustomPlugin>.<CustomCallbackFunction> = (data) => {
//your JS side processing of data returned from your plugin more than once
}
With your values for and
2. Persisting your callbackContext in your custom Plugin to finally finish the call as you already do by calling sendPluginResult()
3.
Upon invocation of your plugin function dont't call sendPluginResult() but instead directly prompt the webview to process JS code where you call your previously in 1. defined callback Function
webView.sendJavascript("cordova.plugins.<pluginPackage>.<CustomPlugin>.<CustomCallbackFunction>('" + <your payload as JSON string> + "')") (Kotlin code)
4. Finally when you are finished calling your callback multiple times call sendPluginResult() on your persisted callbackContext status OK to finish the plugin invocation.
Related
I've got old Android project in Java and I want to extend it with new API from Kotlin. The problem is, when I call more than one suspend function in project, it loses execution point. When suspend function calls another one, it does not return any value, but cuts off all code execution. I ran debugger with "Suspend: All" and still nothing.
In my AuthAPI.java file I've got class with function (for now I've removed callbacks):
public void login(AuthType authType, String password) {
kotlinBackendApi.login(new AuthData(AuthType.PIN, password));
}
In KotlinBackendApi.kt I've created simply Main coroutine scope with launch method, that's my adapter for API:
private val scope = CoroutineScope(Job() + Dispatchers.Main)
fun login(authData: AuthData) {
scope.launch() {
authenticator.authenticate(authData)
}
}
Authenticator contains suspend function authenticate, that firstly call for JWT Token:
override suspend fun authenticate(authData: AuthData) {
withContext(dispatcher) {
val orgAuthToken = getOrganizationToken()
// Other stuff
}
}
Later, getOrganizationToken is another suspend function:
private suspend fun getOrganizationToken(): String {
return tokenJwtApi.getOrganizationToken()
}
It breaks on line return tokenJwtApi.getOrganizationToken(). It does not return any value, like it seems to change coroutine to another one with scope UI, loosing conext in the meantime. Application hangs.
I am building a phonegap application and have called a java native plugin from it using cordova.exec.
I am stuck somewhere unexpected.
I am calling cordova.exec multiple times in a loop.Also those native plugins are performing async task.But to amazement ,its returning callback only for first cordova.exec() call. I want it to return to javascript at my last cordova.exec() call.
Please help.
My code is as follows----
Javascript code-
$( "input:checked" ).each(function()
{
cordova.exec(callbacksuccess,callbackerror,'MyPlugin','plugin1',[path,pckg,id]);
});
function callbacksuccess(e)
{
alert(e);
}
function callbackerror()
{
alert('error');
}
Java Native Plugin code-
if (action.equals("plugin1"))
{
new DownloadManager().execute(myurl);
return true;
}
public class DownloadManager extends AsyncTask<String, String, String>
{
#Override
public String doInBackground(final String... arg0)
{
try
{
downloadapk(arg0[0]);
installapk();
System.out.println("Download Complete");
PluginResult result = new PluginResult(PluginResult.Status.OK, "success");
result.setKeepCallback(true);
callback.success("done");
return null;
}
catch(Exception e)
{
callback.error("Some problem occured.Try again later");
return null;
}
}
}
Suppose I have 5 listitems selected. It returns "done" just once. callbacksuccess function is called just once for the first cordova.exec function.I didnot find any solution.
Thanx in advance
Try this.
1.In your execute() method of your plugin save the callbackId you get and return a NO_RESULT plugin result and set keep callback id to true.
PluginResult pluginResult = new PluginResult(PluginResult.Status.NO_RESULT);
pluginResult.setKeepCallback(true);
return pluginResult;
2.When your async java method finishes return another plugin result like this:
PluginResult result = new PluginResult(PluginResult.Status.OK, data);
result.setKeepCallback(false);
this.success(result, this.myCallbackId);
I'm using Phonegap to develop an app. I've downloaded a camera plugin, however, I'd like to make a Javascript call from within the plugin.
In the Java file for the camera plugin I have done the following;
private class sendJS extends CordovaActivity {
public void sendcommand() {
this.sendJavascript("alert('1337')");
}
}
#Override
public void onClick(View v) {
sendJS test = new sendJS();
test.sendcommand();
}
However, when the onclick is triggered nothing happens...
I've also tried super.sendJavascript() and super.loadUrl() but it didn't work.
Thanks.
You have two ways to comunicate to your javascript code. The first one is injecting code to webview via .loadUrl(...) method. The second one is via a callback in response to a javascript->native-plugin(java) call.
You can see callback response in execYourJavaMethod() and injecting in sendcommand()
private class sendJS extends CordovaActivity {
#Override
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
// Implement here your calls from javascript
boolean result = false;
if ("yourJavaMethod".equals(action)) {
JSONObject options = args.optJSONObject(0);
result = execYourJavaMethod(options, callbackContext);
}
return result;
}
public boolean execYourJavaMethod(JSONObject options, CallbackContext callbackContext) {
// This will inject an event to your javascript code
this.sendcommand();
boolean iWantToCallSuccessCallbackWithData = false;
if (iWantToCallSuccessCallbackWithData) {
// This will call your success callback with some data
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, "Your data back to javascript"));
} else {
// This will call the success callback with no data
callbackContext.success();
}
return true;
}
public void sendcommand() {
String event = String.format("javascript:cordova.fireDocumentEvent('yourEventHere', { 'param1': '%s' });", "some string for param1");
this.webView.loadUrl(event);
}
}
From javascript side you should register the listener for your event:
document.addEventListener('yourEventHere', function(e) {
alert(JSON.stringify(e));
});
To comunicate to your Java plugin:
myPlugin.doSomethingInJava = function (successCallback, failureCallback) {
cordova.exec(successCallback, failureCallback, 'sendJS', 'yourJavaMethod', []);
};
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 call function(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-call AwesomePlugin.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?
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.
In jonas's answer, every time you call sendPluginResult you have to send the same value. So I changed the PluginResult class to add a method like this:
public void setStrMessage(String strMessage){
this.strMessage = strMessage;
}
This way, I can set the message I want to send to the JavaScript side.
How do we call javascript from Android? I have this javascript library which I would like to use, I want to call the javascript function and pass the result value to the android java code. Haven't found the answer from now. i managed to call android code from javascript, but I want the other way around.
There is a hack:
Bind some Java object so that it can be called from Javascript with WebView:
addJavascriptInterface(javaObjectCallback, "JavaCallback")
Force execute javascript within an existing page by
WebView.loadUrl("javascript: var result = window.YourJSLibrary.callSomeFunction();
window.JavaCallback.returnResult(result)");
(in this case your java class JavaObjectCallback should have a method returnResult(..))
Note: this is a security risk - any JS code in this web page could access/call your binded Java object. Best to pass some one-time cookies to loadUrl() and pass them back your Java object to check that it's your code making the call.
You can use Rhino library to execute JavaScript without WebView.
Download Rhino first, unzip it, put the js.jar file under libs folder. It is very small, so you don't need to worry your apk file will be ridiculously large because of this one external jar.
Here is some simple code to execute JavaScript code.
Object[] params = new Object[] { "javaScriptParam" };
// Every Rhino VM begins with the enter()
// This Context is not Android's Context
Context rhino = Context.enter();
// Turn off optimization to make Rhino Android compatible
rhino.setOptimizationLevel(-1);
try {
Scriptable scope = rhino.initStandardObjects();
// Note the forth argument is 1, which means the JavaScript source has
// been compressed to only one line using something like YUI
rhino.evaluateString(scope, javaScriptCode, "JavaScript", 1, null);
// Get the functionName defined in JavaScriptCode
Object obj = scope.get(functionNameInJavaScriptCode, scope);
if (obj instanceof Function) {
Function jsFunction = (Function) obj;
// Call the function with params
Object jsResult = jsFunction.call(rhino, scope, scope, params);
// Parse the jsResult object to a String
String result = Context.toString(jsResult);
}
} finally {
Context.exit();
}
You can see more details at my post.
In order to match the method calls of the iOS WebviewJavascriptBridge ( https://github.com/marcuswestin/WebViewJavascriptBridge ), I made some proxy for the calls of register_handle and call_handle. Please note I am not a Javascript-guru therefore there is probably a better solution.
javascriptBridge = (function() {
var handlers = {};
return {
init: function () {
},
getHandlers : function() {
return handlers;
},
callHandler : function(name, param) {
if(param !== null && param !== undefined) {
JSInterface[name](param);
} else {
JSInterface[name]();
}
},
registerHandler : function(name, method) {
if(handlers === undefined) {
handlers = {};
}
if(handlers[name] === undefined) {
handlers[name] = method;
}
}
};
}());
This way you can send from Javascript to Java calls that can have a String parameter
javascriptBridge.callHandler("login", JSON.stringify(jsonObj));
calls down to
#JavascriptInterface
public void login(String credentialsJSON)
{
Log.d(getClass().getName(), "Login: " + credentialsJSON);
new Thread(new Runnable() {
public void run() {
Gson gson = new Gson();
LoginObject credentials = gson.fromJson(credentialsJSON, LoginObject.class);
SingletonBus.INSTANCE.getBus().post(new Events.Login.LoginEvent(credentials));
}
}).start();
}
and you can call back to Javascript with
javascriptBridge.registerHandler('successfullAuthentication', function () {
alert('hello');
})
and
private Handler webViewHandler = new Handler(Looper.myLooper());
webViewHandler.post(
new Runnable()
{
public void run()
{
webView.loadUrl("javascript: javascriptBridge.getHandlers().successfullAuthentication();"
}
}
);
If you need to pass a parameter, serialize to JSON string then call StringEscapeUtils.escapeEcmaScript(json), otherwise you get unexpected identifier: source (1) error.
A bit tacky and hacky, but it works. You just have to remove the following.
connectWebViewJavascriptBridge(function(bridge) {
}
EDIT:
in order to change the global variable to an actual property, I changed the above code to the following:
(function(root) {
root.bridge = (function() {
var handlers = {};
return {
init: function () {
},
getHandlers : function() {
return handlers;
},
callHandler : function(name, param) {
if(param !== null && param !== undefined) {
Android[name](param);
} else {
Android[name]();
}
},
registerHandler : function(name, method) {
if(handlers === undefined) {
handlers = {};
}
if(handlers[name] === undefined) {
handlers[name] = method;
}
}
};
}());
})(this);
I got the idea from Javascript global module or global variable .
For a full implementation of JavaScript that doesn't require using a slow WebView, please see AndroidJSCore, which is a full port of Webkit's JavaScriptCore for Android.
UPDATE 2018: AndroidJSCore is deprecated. However, its successor, LiquidCore has all of the same functionality and more.
Calling functions from Android is very simple:
JSContext context = new JSContext();
String script =
"function factorial(x) { var f = 1; for(; x > 1; x--) f *= x; return f; }\n" +
"var fact_a = factorial(a);\n";
context.evaluateScript("var a = 10;");
context.evaluateScript(script);
JSValue fact_a = context.property("fact_a");
System.out.println(df.format(fact_a.toNumber())); // 3628800.0