So, everybody knows that we make a Class extending CordovaPlugin
and override the execute()
and then creates a bridge between the JS and native Java (for Android). Further we use PluginResult
to return the result back to the JS.
So, all of this happens when there is a request fired from the JS to the Java Plugin. My question is, how to send a result back to JS (and therefore to HTML) asynchronously ?
I don't know if the word asynchronous is right here. The thing is I want to send something back to the JS out of the blue (say, when wifi becomes enable/disable).
I have already researched on this but haven't got anything which suits to my case.
The thing I've tried is -
BroadcastReceiver
listening to the WiFi
events using the WifiManager
class.
Toast
when WiFi
is enabled/disabled, and sending the result using CallbackContext
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, "Wifi
Connected"))
and for disconnected with a different message.MyPlugin.java
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.PluginResult;
import org.json.JSONArray;
...
public class MyPlugin extends CordovaPlugin {
private WifiReceiver wifiBroadcastReceiver = null;
private CallbackContext callbackContext = null;
...
public MyPlugin() {
wifiBroadcastReceiver = new WifiReceiver();
...
}
...
public boolean execute(String action, final JSONArray args,
final CallbackContext callbackId) throws JSONException {
IntentFilter wifiFilter = new IntentFilter(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION);
cordova.getActivity().registerReceiver(wifiBroadcastReceiver, wifiFilter);
this.callbackContext = callbackId;
...
}
public class WifiReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (action.equals(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION)) {
if (intent.getBooleanExtra(WifiManager.EXTRA_SUPPLICANT_CONNECTED, false)) {
Toast.makeText(cordova.getActivity(), "Wifi Connected", Toast.LENGTH_SHORT).show();
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, "Wifi Connected"));
} else {
Toast.makeText(cordova.getActivity(), "Wifi Disconnected", Toast.LENGTH_SHORT).show();
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, "Wifi Disconnected"));
}
}
}
}
The Toast
pops but the PluginResult
isn't sent to the JS.
PS : Listening to WiFi events isn't my actual problem, I want to replicate the Android Bluetooth Chat
app in Phonegap. So, it has to be asynchronous in nature.
You are almost there but you need to setKeepCallback to true on your PluginResult. If you don't the subsequent results from the Java side will not have a callback on the JavaScript side. The best example of this type of coding is the Network plugin in Cordova core. Here is a link to the source:
https://git-wip-us.apache.org/repos/asf?p=cordova-plugin-network-information.git;a=blob;f=src/android/NetworkManager.java;h=e2ac500ccc885db641d5df6dab8eae23026a5828;hb=HEAD
So you should update your code to:
public boolean execute(String action, final JSONArray args,
final CallbackContext callbackId) throws JSONException {
IntentFilter wifiFilter = new IntentFilter(
WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION);
cordova.getActivity().registerReceiver(wifiBroadcastReceiver,
wifiFilter);
this.callbackContext = callbackId;
PluginResult result = new PluginResult(PluginResult.Status.NO_RESULT);
result.setKeepCallback(true);
this.callbackContext.sendPluginResult(result);
return true;
}
public class WifiReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (action.equals(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION)) {
PluginResult result;
if (intent.getBooleanExtra(
WifiManager.EXTRA_SUPPLICANT_CONNECTED, false)) {
Toast.makeText(cordova.getActivity(), "Wifi Connected",
Toast.LENGTH_SHORT).show();
result = new PluginResult(PluginResult.Status.OK,
"Wifi Connected");
} else {
Toast.makeText(cordova.getActivity(), "Wifi Disconnected",
Toast.LENGTH_SHORT).show();
result = new PluginResult(PluginResult.Status.ERROR,
"Wifi Disconnected");
}
result.setKeepCallback(false);
if (callbackContext != null) {
callbackContext.sendPluginResult(result);
callbackContext = null;
}
}
}
}
Answer to 'second callback' warning...
The Cordova source-code which triggers this warning can be found on line 57 here:
https://github.com/apache/cordova-android/blob/master/framework/src/org/apache/cordova/CallbackContext.java
Thus - warning is caused because your CallbackContext object has 'finished=true'.
Most likely cause of this is you called: callbackContext.sendPluginResult(pluginResult);
Without first calling: pluginResult.setKeepCallback(true);
If not... most likely you are unintentionally caching the CallbackContext object.
Your execute() function should assign CallbackContext each time it is called. See lines 125-127 in the code Simon linked to:
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) {
if (action.equals("getConnectionInfo")) {`
this.connectionCallbackContext = callbackContext;
...
The proper sequence of events in full:
Make initial call to plugin.
Plugin saves reference to passed in CallbackContext object.
Keep CallbackContext object reference, while returning results with setKeepCallback(true).
When the sequence is finished, return with setKeepCallback(false) (the default)
Then later...
Make another call to plugin.
Plugin overwrites saved CallbackContext reference, replace with passed in object.
Then steps 3-4 same as above.
Hope that helps :)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With