why is the WebView.evaluateJavascript method called twice? - javascript

public static String dataFromJs;
public void androidMethod() {
// initialize static String dataFromJs.
dataFromJs = "";
webView.evaluateJavascript("javascript:jsFuction();", new ValueCallback<String>() {
#RequiresApi(api = Build.VERSION_CODES.O)
#Override
public void onReceiveValue(String s) {
JsonReader reader = new JsonReader(new StringReader(s));
reader.setLenient(true);
try {
if (reader.peek() != JsonToken.NULL) {
if (reader.peek() == JsonToken.STRING) {
// get string value from javascript and I want to use the value
dataFromJs = reader.nextString();
}
}
} catch (IOException e) {
Log.e("TAG", "MainActivity: IOException", e);
} finally {
try {
reader.close();
} catch (IOException e) {
// NOOP
}
}
}
});
#RequiresApi(api = Build.VERSION_CODES.KITKAT)
#Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
if(url.endsWith("menu/page.do")) {
androidMethod();
// do something with dataFromJs..
}
}
The androidMethod is my method in android.
I try to use the str what I receieved from javascript.
but webView.evaluateJavascript method always return null at the first time,
the webView.evaluateJavascript is called again and return string value correctly.
Since the return value is null at the first time, so I can't use the androidMethod in another method.
Anyone has good solution??
is it have something to do with this log (The application may be doing too much work on its main thread.) ?
Thank you!

Related

how to run a c# method from js inside of the webview app in xamarin

I have a problem with running a c# method from js that is inside of a webview in xamarin.
I have a c# method like this:
public void simpleMethod(int a,string b,bool c){
string[] data;
if(c){
data[a] = b;
}
}
Now how do I call this method from the javasript?
You can create a Custom Renderer of WebView
Create a custom WebView in Forms
public class HybridWebView : View
{
Action<int, string, bool> action;
public static readonly BindableProperty UriProperty = BindableProperty.Create (
propertyName: "Uri",
returnType: typeof(string),
declaringType: typeof(HybridWebView),
defaultValue: default(string));
public string Uri {
get { return (string)GetValue (UriProperty); }
set { SetValue (UriProperty, value); }
}
public void RegisterAction (Action<int, string, bool> callback)
{
action = callback;
}
public void Cleanup ()
{
action = null;
}
public void InvokeAction (int a,string b,bool c)
{
if (action == null ) {
return;
}
action.Invoke (a,b,c);
}
}
in ContentPage
The HybridWebView instance will be used to display a native web control on each platform. It's Uri property is set to an HTML address , and which will be displayed by the native web control.
The HybridWebViewPage registers the action to be invoked from JavaScript, as shown in the following code example:
public partial class xxxPage : ContentPage
{
public xxxPage ()
{
//...
hybridWebView.RegisterAction ((a,b,c) => simpleMethod(a,b,c));
}
public void simpleMethod(int a,string b,bool c)
{
string[] data;
if(c){
data[a] = b;
}
}
}
<ContentPage.Content>
<local:HybridWebView x:Name="hybridWebView" Uri="https://learn.microsoft.com"
HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" />
</ContentPage.Content>
in your html file
For example you want to call the method when click a button
<button type="button" onclick="javascript:invokeCSCode(a,b,c);">Invoke C# Code</button>
//...
<script type="text/javascript">
function invokeCSCode(a,b,c) {
try {
invokeCSharpAction(a,b,c);
}
catch (err){
log(err);
}
}
Creating the Custom Renderer on iOS
[assembly: ExportRenderer (typeof(HybridWebView), typeof(HybridWebViewRenderer))]
namespace CustomRenderer.iOS
{
public class HybridWebViewRenderer : ViewRenderer<HybridWebView, WKWebView>, IWKScriptMessageHandler
{
const string JavaScriptFunction = "function invokeCSharpAction(a,b,c){window.webkit.messageHandlers.invokeAction.postMessage(a,b,c);}";
WKUserContentController userController;
protected override void OnElementChanged (ElementChangedEventArgs<HybridWebView> e)
{
base.OnElementChanged (e);
if (e.OldElement != null) {
userController.RemoveAllUserScripts ();
userController.RemoveScriptMessageHandler ("invokeAction");
var hybridWebView = e.OldElement as HybridWebView;
hybridWebView.Cleanup ();
}
if (e.NewElement != null) {
if (Control == null) {
userController = new WKUserContentController ();
var script = new WKUserScript (new NSString (JavaScriptFunction), WKUserScriptInjectionTime.AtDocumentEnd, false);
userController.AddUserScript (script);
userController.AddScriptMessageHandler (this, "invokeAction");
var config = new WKWebViewConfiguration { UserContentController = userController };
var webView = new WKWebView (Frame, config);
SetNativeControl (webView);
}
Control.LoadRequest (new NSUrlRequest (new NSUrl (Element.Uri, false)));
}
}
public void DidReceiveScriptMessage (WKUserContentController userContentController, WKScriptMessage message)
{
Element.InvokeAction (message.Body.ToString ());
}
}
}
In addition, Info.plist must be updated to include the following values:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
Creating the Custom Renderer on Android
[assembly: ExportRenderer(typeof(HybridWebView), typeof(HybridWebViewRenderer))]
namespace CustomRenderer.Droid
{
public class HybridWebViewRenderer : ViewRenderer<HybridWebView, Android.Webkit.WebView>
{
const string JavascriptFunction = "function invokeCSharpAction(a,b,c){jsBridge.invokeAction(a,b,c);}";
Context _context;
public HybridWebViewRenderer(Context context) : base(context)
{
_context = context;
}
protected override void OnElementChanged(ElementChangedEventArgs<HybridWebView> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
Control.RemoveJavascriptInterface("jsBridge");
var hybridWebView = e.OldElement as HybridWebView;
hybridWebView.Cleanup();
}
if (e.NewElement != null)
{
if (Control == null)
{
var webView = new Android.Webkit.WebView(_context);
webView.Settings.JavaScriptEnabled = true;
webView.SetWebViewClient(new JavascriptWebViewClient($"javascript: {JavascriptFunction}"));
SetNativeControl(webView);
}
Control.AddJavascriptInterface(new JSBridge(this), "jsBridge");
Control.LoadUrl(Element.Uri);
}
}
}
}
public class JavascriptWebViewClient : WebViewClient
{
string _javascript;
public JavascriptWebViewClient(string javascript)
{
_javascript = javascript;
}
public override void OnPageFinished(WebView view, string url)
{
base.OnPageFinished(view, url);
view.EvaluateJavascript(_javascript, null);
}
}
public class JSBridge : Java.Lang.Object
{
readonly WeakReference<HybridWebViewRenderer> hybridWebViewRenderer;
public JSBridge (HybridWebViewRenderer hybridRenderer)
{
hybridWebViewRenderer = new WeakReference <HybridWebViewRenderer> (hybridRenderer);
}
[JavascriptInterface]
[Export ("invokeAction")]
public void InvokeAction (int a,string b,bool c)
{
HybridWebViewRenderer hybridRenderer;
if (hybridWebViewRenderer != null && hybridWebViewRenderer.TryGetTarget (out hybridRenderer))
{
hybridRenderer.Element.InvokeAction (a,b,c);
}
}
}
You don't.
Basically you have to detect / trigger a piece of javascript code, detect it with your C# webview configuration/initialization code, and then execute your C#.
Take a look at https://developer.android.com/reference/android/webkit/JavascriptInterface for android to understand what I'm saying, and implement it on Android.
For iOS you have a WKWebview listener that you can use to receive informations from your webpage.

Android & Javascript - From two functions to one

MainActivity.java
public String URI = null;
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 0 && resultCode == RESULT_OK)
{
Uri pickedImage = data.getData();
URI = pickedImage.toString();
}
}
public String GetURI()
{
return URI;
}
WebAppInterface.java
#JavascriptInterface
public void GetPicture()
{
Intent galleryIntent = new Intent(Intent.ACTION_PICK);
galleryIntent.setType("image/*");
((MainActivity) mContext).startActivityForResult(galleryIntent, 0);
}
#JavascriptInterface
public String GetURI()
{
return getRealPathFromURI(mContext, Uri.parse(((MainActivity) mContext).GetURI()));
}
I'm using the following Javascript functions
function GetPicture()
{
Android.GetPicture();
}
function loadImage()
{
document.getElementById("img").src = Android.GetURI();
}
The first one is used to get the picture and store the full path in a string. The second function is used to obtain the the full path from the string.
Currently this is working because I'm first calling GetPicture and then loadImage.
But I want to "merge" the functions.
So the Javascript would be:
function loadImage()
{
document.getElementById("img").src = Android.GetPicture();
}
So I changed the code from WebAppInterface.java to this:
#JavascriptInterface
public String GetPicture()
{
Intent galleryIntent = new Intent(Intent.ACTION_PICK);
galleryIntent.setType("image/*");
((MainActivity) mContext).startActivityForResult(galleryIntent, 0);
return ((MainActivity) mContext).GetURI();
}
But it's not working. I think I know why because GetURI is being called before the string URI has been set.
So how do I fix this problem?
If somebody knows a better title please let me know.
OK I think I found a solution but I don't know if this is the best way to do it.
So for those who want to know how I fixed it:
I changed the GetPicture method
#JavascriptInterface
public String GetPicture()
{
Intent galleryIntent = new Intent(Intent.ACTION_PICK);
galleryIntent.setType("image/*");
((MainActivity) mContext).startActivityForResult(galleryIntent, 0);
while(((MainActivity) mContext).IsFinished == false){}
return ((MainActivity) mContext).GetURI();
}
In MainActivity.java I added a public boolean called IsFinished (set to false) and it is going to be true when the user picked an image (onActivityResult).
So the code will be:
public String URI = null;
public boolean IsFinished = false;
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 0 && resultCode == RESULT_OK)
{
Uri pickedImage = data.getData();
URI = pickedImage.toString();
IsFinished = true;
}
}
public String GetURI()
{
return URI;
}

Handling native event in javascript android

I have written a native class to download from the internet, and I want to call that code from JavaScript. I can call the native code from JavaScript in webview, but I don't know how to call the native event in JavaScript.
This is the native code :
public class DonwloadTask extends AsyncTask<URL,Void,Long> {
private OnTaskCompleted listener;
private String result;
public DonwloadTask(OnTaskCompleted listener){
this.listener=listener;
}
protected Long doInBackground(URL... urls) {
int count = urls.length;
long totalSize = 0;
StringBuilder resultBuilder = new StringBuilder();
for (int i = 0; i < count; i++) {
try {
// Read all the text returned by the server
InputStreamReader reader = new InputStreamReader(urls[i].openStream());
BufferedReader in = new BufferedReader(reader);
String resultPiece;
while ((resultPiece = in.readLine()) != null) {
resultBuilder.append(resultPiece);
}
in.close();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
// if cancel() is called, leave the loop early
if (isCancelled()) {
break;
}
}
// save the result
this.result = resultBuilder.toString();
return totalSize;
}
protected void onProgressUpdate(Integer... progress) {
// update progress here
}
// called after doInBackground finishes
protected void onPostExecute(Long result) {
//Log.v("API", this.result);
// put result into a json object
try {
JSONObject jsonObject = new JSONObject(this.result);
// call callback
listener.onTaskCompleted(jsonObject);
} catch (JSONException e) {
e.printStackTrace();
}
}
</code>
And this is interface for event
<code>
public interface OnTaskCompleted {
String onTaskCompleted(JSONObject result);
}
</code>
And this is class wrapped the DownloadTask
<code>
public class NativeApi implements OnTaskCompleted {
private DonwloadTask task;
public NativeApi(){
task= new DonwloadTask(this);
}
public void startDownload(String urlStr){
URL url= null;
try {
url = new URL(urlStr);
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
#Override
public String onTaskCompleted(JSONObject result) {
return result.toString();
}
}
</code>
And javascript to call native code
<code>
function startDownload(url){
NativeApi.startDownload(url);
}
</code>
How can I subscribe onTaskCompleted in JavaScript?
You can make a call as follws:
MyActivity.activity.sendJavascript('yourFunction();');
Check this link for further info Calling javascript function from the android activity

How to wait for the child activity to finish

I am trying to make a phonegap/cordova plugin which is for converting speech to text using RecognizerIntent. The following is my code of the plugin class:
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
this.callbackContext = callbackContext;
try {
if (ACTION_INVOKE_SPEECH_RECOG.equals(action)) {
//JSONObject arg_object = args.getJSONObject(0);
Intent calIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
calIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, "en-US");
this.cordova.setActivityResultCallback(SpeechToTextPlugin.this);
this.cordova.getActivity().startActivityForResult(calIntent,REQUEST_CODE );
callbackContext.success("Completed Main Activity");
}
callbackContext.error("Invalid action");
return false;
} catch(Exception e) {
System.err.println("Exception: " + e.getMessage());
callbackContext.error(e.getMessage());
return false;
}
}
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
if (resultCode == Activity.RESULT_OK) {
JSONObject obj = new JSONObject();
try {
//obj.put("TEXT", intent.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS).toString());
callbackContext.success(intent.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS).toString());
}catch(JSONException e){
}
super.onActivityResult(requestCode, resultCode, intent);
}
}
I pass the recognized text to the javascript invoking the execute method. Right now, after making a call to startActivityForResult, the execute function returns back. How do I pass the recognized word to the js?
You have to use
sendJavascript("yourJSClass.yourJSMethod();");
or just
sendJavascript("yourJSMethod();");
Of course you will have to have:
yourJSMethod = function() {}
in your JS

How to create Async Task in JavaScript

In my android application, i have called one javascript method from html page to java page.
this.start = function ()
{
try
{
Mynamespace.myapi.getvalue("myvalue", {
onSuccess: function (data)
{
value= JSON.parse(data);
alert("called");
},
onFailure: function (error) { }
});
}
catch (err) { }
if (value== null) { value= new Object();
};
In the above, getvalue method is called in my java class and returned some values, but before getting the value inside the OnSuccess/OnFailure, the below line is called. How to wait the method up to the callback is called?
if (value== null) { value= new Object();
private class LongOperation extends AsyncTask<String, Void, String>
{
protected void onPreExecute()
{
progressDialog = new ProgressDialog(activity.this);
progressDialog.setTitle("Processing...");
progressDialog.setMessage("Please wait...");
progressDialog.setCancelable(true);
progressDialog.show();
}
protected String doInBackground(String... params)
{
try
{
//Getting data from server
}
catch (Exception e) {
e.printStackTrace();
}
return null;
}
protected void onPostExecute(String result)
{
progressDialog.dismiss();
}
}
How to call this
ProgressDialog progressDialog;
LongOperation mytask = null;
mytask = new LongOperation();
mytask.execute();
I have analyzed more about my query. Finally i got a solution. String that i have loaded with webview was having more single quotes so unable to load with webview. So instead of calling Success failure callback, the next line is called.
Now I have replaced the single quotes "\'" by "\'" in my java class. Working fine. :-)

Categories

Resources