Javascript calling Android methods in WebView - javascript

I have some javascript running in WebView. In this Javascript code there a function which returns a boolean. I want to check the return value from this function and depends on it hide or not a view in my android code. I tried for one day and it does not work. Do someone knows where is my error? This is my code:
public class MyActivity extends Activity {
private static final String JS_INTERFACE = "Android";
....
webView.getSettings().setJavaScriptEnabled(true);
webView.setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY);
webView.loadUrl(getUrl(this.getResources().getString(R.string.host)));
webView.addJavascriptInterface(new WebViewJavaScriptInterface(this), JS_INTERFACE);
webView.setWebViewClient(new WebViewClient(progressBar, this, tvError));
webView.setWebChromeClient(new WebChromeClient(progressBar));
webView.loadUrl("javascript:window.Android.showAdBanner(showSdkAd())");
}
public class WebViewJavaScriptInterface
{
....
#JavascriptInterface
public void showAdBanner(String jsResult) {
if (jsResult == "true") {
((Activity) context).findViewById(R.id.adView).setVisibility(View.GONE);
} else {
((Activity) context).findViewById(R.id.adView).setVisibility(View.GONE);
}
}
}

You're setting the visibility to View.GONE in both cases of the if (jsResult == "true") if statement.
I think the window in the js is unneeded, so
webView.loadUrl("javascript:window.Android.showAdBanner(showSdkAd())");
Should be
webView.loadUrl("javascript:Android.showAdBanner(showSdkAd())");
Also, the javascript callback will be executed in a background thread, so you need to move to the main thread (posting a runnable to a view, runOnUiThread, using a handler etc), before performing Ui operations.
If you have a reference to a View, you can do:
#JavascriptInterface
public void showAdBanner(String jsResult) {
viewReference.post(new Runnable() {
public void run() {
if (jsResult == "true") {
((Activity) context).findViewById(R.id.adView).setVisibility(View.GONE);
} else {
((Activity) context).findViewById(R.id.adView).setVisibility(View.GONE);
}
}
}
Since, you have a reference to the activity, you can replace viewReference.post with ((Activity) context).runOnUiThread
If you initialise a Handler on the main thread, it will be bound to the main thread. As a field of the Activity, you could have:
private Handler mHandler = new Handler();
And then replace viewReference.post with mHandler.post
You could also make a custom Handler that implements handleMessage(Message msg) and then you can just send it an empty message. However, you should read https://techblog.badoo.com/blog/2014/08/28/android-handler-memory-leaks/ to avoid memory issues.

Related

kotlin.UninitializedPropertyAccessException: lateinit property WebView has not been initialized

I created an android app and loading an html file on android webView. Which is loaded successfully and working fine.
class MainActivity : AppCompatActivity() {
private lateinit var myAndroidWebView: WebView;
#SuppressLint("SetJavaScriptEnabled")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setWebviewDetails();
}
private fun setWebviewDetails(){
//if(!::myAndroidWebView.isInitialized){
myAndroidWebView = findViewById(R.id.webView);
//}
myAndroidWebView.settings.javaScriptEnabled = true;
myAndroidWebView.loadUrl("file:///android_asset/App/index.html");
myAndroidWebView.addJavascriptInterface(WebAppInterface(this), "AndroidApp");
}
public fun testMessage(param: String){
println("Interface call-2")
myAndroidWebView.post(Runnable {
val str = "xxxXXXXXXXXXXXXXx $param"
myAndroidWebView.loadUrl("javascript:Application.UserInterface.sample('$str')")
})
println("Interface call-3")
}
}
Now I want to send message to Android app to JS and vice versa.
I have a button in HTML and triggeting the function
public fun showToast(toast: String) {}
From HTML view by using, AndroidApp.showToast("hello");
Which is working fine and I am getting call from JS to Android interface function showToast()
Now on request from the JS I want to get some values from Android and send back to JS as well.
I have an interface, on trigger button from HTML I am getting call on the below interface function.
And trying to call a method in the MainActivity, public fun testMessage(param: String){} is triggered succssfully.
Issue:
I am trying to send data to JS by using,
myAndroidWebView.loadUrl("javascript:Application.UserInterface.sample('$str')")
Here I am getting error.
W/System.err: kotlin.UninitializedPropertyAccessException: lateinit property myAndroidWebView has not been initialized
How do I resolve it.
Thanks.
/** Instantiate the interface and set the context */
class WebAppInterface(private val mContext: Context) {
var mainActivity:MainActivity = MainActivity();
/** Show a toast from the web page */
#JavascriptInterface
public fun showToast(toast: String) {
println("Interface call-1")
mainActivity.testMessage(mContext,toast);
}
}
lateinit property not initialized exception is thrown because you are trying to create an instance of MainActivity in WebInterface. var mainActivity:MainActivity = MainActivity();
It is Android system's job to create and load your activities. You should never try to initiate an activity.
Here, a rough improvement of your code. Try to adapt it to your needs.
interface JsCommunicator {
fun testMessage(param: String)
}
class WebAppInterface(private val communicator: JsCommunicator) {
#JavascriptInterface
fun showToast(toast: String) {
communicator.testMessage(toast)
}
}
class YourMainActivity : JsCommunicator {
// ...
private lateinit var myAndroidWebView: WebView
override fun testMessage(param: String) {
println("Interface call-2")
myAndroidWebView.post(Runnable {
val str = "xxxXXXXXXXXXXXXXx $param"
myAndroidWebView.loadUrl("javascript:Application.UserInterface.sample('$str')")
})
println("Interface call-3")
}
}
You are accessing web-view without initialising it.
call setWebviewDetails() first then testMessage()
or you can make webview nullable like this
private var myAndroidWebView: WebView? = null;
also you need to call startActivity() to create Activity not by
creating objects as this is framework class managed by Android
System.

onBackPressed event is not working properly in webview

I am developing an android app using webview. after a button is clicked it navigates to other page. i used javascript function onClick() to navigate.when back button is pressed it used to close that application but i want it to navigate to previous page. after using following code it states "unfortunately, myapp stopped working". I tried all possible ways. please help me
package com.example.anush.teamcetrankevaluator;
import android.app.Activity;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.webkit.WebView;
public class MainActivity extends Activity {
WebView web;
#Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
WebView web=(WebView)this.findViewById(R.id.webview);
web.loadUrl("file:///android_asset/webview.html");
web.getSettings().setJavaScriptEnabled(true);
//web.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
#Override
public void onBackPressed() {
super.onBackPressed();
if (web.canGoBack()) {
web.goBack();
}
else
{
super.onBackPressed();
finish();
}
}
#Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
// TODO Auto-generated method stub
// finish the activity
if (keyCode == KeyEvent.KEYCODE_BACK && web.canGoBack()) {
web.goBack();
return true;
}
return super.onKeyDown(keyCode, event);
}
}
Logcat Error :
09-10 20:44:52.688 10542-10542/? D/AndroidRuntime﹕ Shutting down VM
09-10 20:44:52.690 10542-10542/? E/AndroidRuntime﹕ FATAL EXCEPTION: main
Process: com.example.anush.teamcetrankevaluator, PID: 10542
java.lang.NullPointerException: Attempt to invoke virtual method 'boolean android.webkit.WebView.canGoBack()' on a null object reference
at com.example.anush.teamcetrankevaluator.MainActivity.onKeyDown(MainActivity.java:65)
at android.view.KeyEvent.dispatch(KeyEvent.java:2622)
at android.app.Activity.dispatchKeyEvent(Activity.java:2714)
at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:2221)
at android.view.ViewRootImpl$ViewPostImeInputStage.processKeyEvent(ViewRootImpl.java:3988)
at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:3950)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3509)
at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:3562)
at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:3528)
at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:3638)
at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:3536)
at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:3695)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3509)
at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:3562)
at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:3528)
at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:3536)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3509)
at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:3562)
at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:3528)
at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:3671)
at android.view.ViewRootImpl$ImeInputStage.onFinishedInputEvent(ViewRootImpl.java:3832)
at android.view.inputmethod.InputMethodManager$PendingEvent.run(InputMethodManager.java:2210)
at android.view.inputmethod.InputMethodManager.invokeFinishedInputEventCallback(InputMethodManager.java:1851)
at android.view.inputmethod.InputMethodManager.finishedInputEvent(InputMethodManager.java:1842)
at android.view.inputmethod.InputMethodManager$ImeInputEventSender.onInputEventFinished(InputMethodManager.java:2187)
at android.view.InputEventSender.dispatchInputEventFinished(InputEventSender.java:141)
at android.os.MessageQueue.nativePollOnce(Native Method)
at android.os.MessageQueue.next(MessageQueue.java:143)
at android.os.Looper.loop(Looper.java:122)
at android.app.ActivityThread.main(ActivityThread.java:5312)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:901)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:696)
09-10 20:44:54.495 10542-10542/? I/Process﹕ Sending signal. PID: 10542 SIG: 9
Edit your onBackPressed to look like this:
#Override
public void onBackPressed() {
// get rid of this --> super.onBackPressed();
if (web.canGoBack()) {
web.goBack();
}
else
{
super.onBackPressed();
// get rid of this --> finish();
}
}
You've also got an error in onCreate:
WebView web;
#Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// this line is wrong: WebView web=(WebView)this.findViewById(R.id.webview);
//should be:
web=(WebView)this.findViewById(R.id.webview);
web.loadUrl("file:///android_asset/webview.html");
web.getSettings().setJavaScriptEnabled(true);
//web.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);
}
This line is the useful part of your logcat error:
java.lang.NullPointerException: Attempt to invoke virtual method 'boolean android.webkit.WebView.canGoBack()' on a null object reference
It means that web was null, but you were trying to do web.canGoBack();
You create your variable WebView web in the main activity and then declare the same variable in the oncreate method. By doing this, you cannot access this initialised variable web outside of the scope of the oncreate bundle.
So the variable that you are attempting to access, the class variable WebView web that you declared first is not initialised, this is what is causing your app to stop.
So as the other answer Joe says, just
web=(WebView)this.findViewById(R.id.webview);
within your oncreate bundle.
This is a link which discusses variable scope in java.
http://www.java-made-easy.com/variable-scope.html
edit just saw your logcat:
java.lang.NullPointerException: Attempt to invoke virtual method 'boolean android.webkit.WebView.canGoBack()' on a null object reference
at com.example.anush.teamcetrankevaluator.MainActivity.onKeyDown(MainActivity.java:65)
at android.view.KeyEvent.dispatch(KeyEvent.java:2622)
The null object reference is referring to the uninitialised Webview web, when there is an attempt to access it in your onKeyDown method.

Android How to detect double tap?

I'm trying to refresh Canvas on DoubleTap in android. I use GestureDetector in custom View.
final GestureDetector mDetector = new GestureDetector(
getContext(), new GestureDetector.OnGestureListener() {
#Override
public boolean onDoubleTap(MotionEvent e) {
invalidate();
return true;
}
}
But I'm getting the error
The method onDoubleTap(MotionEvent) of type new
GestureDetector.OnGestureListener(){} must override or implement a
supertype method
with
Remove '#Override' annotation
solution. I remove override and get this warning
The method onDoubleTap(MotionEvent) from the type new
GestureDetector.OnGestureListener() {} is never used locally.
Then I tried to test whether this works and made a function to change TextView string whenever I DoubleTap. Nothing happens.
I also looked at GestureDetector Reference for explanations, but they don't even have DoubleTap there, which everybody uses. What should I do?
try this
final GestureDetector mDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener {
#Override
public boolean onDown(MotionEvent e) {
return true;
}
#Override
public boolean onDoubleTap(MotionEvent e) {
return true;
}
});
For the ones, who were wondering how to set it also to the corresponding view:
final GestureDetector gDetector = new GestureDetector(getBaseContext(), new GestureDetector.SimpleOnGestureListener() {
#Override
public boolean onDown(MotionEvent e) {
return true;
}
#Override
public boolean onDoubleTap(MotionEvent e) {
doIt();
return true;
}
});
// Set it to the view
mButton.setOnTouchListener((v, event) -> gDetector.onTouchEvent(event));
My approach to this problem was different since I needed to perform something for the onClick listener as well, and also it was in a list view, so I needed to know what was the item content.
here is my approach using kotlin Job:
At the top of the class I've declared something like this:
private var doubleTapTimerJob: Job = Job()
private var clickedViewItem: CartViewItem? = null
val DOUBLE_TAP_DELAY = 200L
where CartViewItem is the model that is used in the list.
and this is my onClickListener logic:
if (clickedViewItem == null || clickedViewItem != cartViewItem) {
doubleTapTimerJob.cancel()
doubleTapTimerJob = lifecycleScope.launch {
delay(DOUBLE_TAP_DELAY)
clickedViewItem = null
}
clickedViewItem = cartViewItem
onClicked(cartViewItem)
} else {
onDoubleClicked(cartViewItem)
clickedViewItem = null
doubleTapTimerJob.cancel()
}
here I wait for 200 milliseconds for the second tap, and if it didn't happen, I will make clickedViewItem null, so its not valid anymore

Android to javascript response

I'm developing an hybrid app in Android. I want to show an alertDialog(instead of an alert/confirm JavaScript function) and when the user touch YES, return a true response to JavaScript, otherwise return false. Then if it's true I want to redirect to another URL.
Here's the 'binding JavaScript code to Android code':
public class WebAppInterface {
Context mContext;
boolean result;
/** Instantiate the interface and set the context */
WebAppInterface(Context c) {
mContext = c;
}
/** Show a toast from the web page */
#JavascriptInterface
public boolean showAlert(String title, String message) {
new AlertDialog.Builder(mContext)
.setTitle(title)
.setMessage(message)
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
result = true;
}
})
.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
result = false;
}
})
.show();
return result;
}
}
And the JavaScript part:
if (Android.showAlert('Title example', 'Are you sure you wanna exit?') == true) {
window.location = "http://newurl.com/example.html";
}
It actually works but not as it supposed to do. JavaScript receives the result when I open again the alertDialog and then redirect to the new URL, It needs to redirect at the moment the user touch the YES button.
I've done lot of research on this particular case and sadly found nothing.
Thank you.
Here is the Javascript Code. Add a function to navigate to a page. Like doNavigate:
<script type="text/javascript">
function showAlert(toast) {
navigate = Android.showAlert('Title example', 'Are you sure you wanna exit?');
}
function doNavigate(){
window.location = "http://www.google.com"
}
</script>
And here is the Android code for show alert. Call the javascript method from onClick positive button:
#JavascriptInterface
public boolean showAlert(String title, String message) {
new AlertDialog.Builder(mContext)
.setTitle(title)
.setMessage(message)
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
((Activity)mContext).runOnUiThread(new Runnable() {
#Override
public void run() {
myWeb.loadUrl("javascript:doNavigate()");
}
});
result = true;
}
})
.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
result = false;
}
})
.show();
return result;
}
Keep a reference of the WebView in your WebAppInterface:
public static class WebAppInterface{
Context mContext;
boolean result;
WebView myWeb;
/** Instantiate the interface and set the context */
WebAppInterface(Context c, WebView webView) {
mContext = c;
myWeb = webView;
}
Probably when on click gets executed it doesnt immediately return the desired boolean value.
I'm thinking the next time you open the dialog then the if statment gets executed.
Maybe you can try injecting the logic through the constructor using an interface or class. And when the positive button is clicked it will call a method of that interface setting window.location
Hope it helps . Good luck

How to call Ajaxdecorator or javascript in onSubmit method of AjaxLink (Wicket)

public class engageLink extends AjaxLink{
private Engage engage;
private String name;
engageLink(String string, Engage anEngage,String name) {
super(string);
this.engage = anEngage;
this.name = name;
hasEngage=((Application) getApplication()).getVtb().hasEngagement(engage,name);
if(hasEngage)
this.add(new AttributeAppender("onclick", new Model("alert('This is my JS script');"), ";"));
}
boolean randevuAlmis;
#Override
public void onClick(AjaxRequestTarget target) {
if(hasEngage){
//do nothing or call ajax on failure script
} else{
((Application) getApplication()).getVtb().addEngagement(engage, name);
}
setResponsePage(new Sick(name));
}
#Override
protected org.apache.wicket.ajax.IAjaxCallDecorator getAjaxCallDecorator()
{
return new AjaxCallDecorator()
{
#Override
public CharSequence decorateOnSuccessScript(CharSequence script)
{
return "alert('Success');";
}
#Override
public CharSequence decorateOnFailureScript(CharSequence script)
{
return "alert('Failure');";
}
};
};
}
This is my code.IN the method on click i call ajax onfailure script .but it doesn't work.
I tried adding javascript in the constructor.It does not work too.
What is the problem.
Note i call ajaxdecorator like;
getAjaxCallDecorator().decorateOnFailureScript("some message");
How can i solve these problems.
Thanks
Are you trying to call the failure script without a failure? If that's the case, you could call:
target.appendJavascript("alert('Failure');");
or
target.appendJavascript(getAjaxCallDecorator().decorateOnFailureScript("some message"));
BUT, you are calling setResponsePage() at the end of the onClick() method, I think that could block any scripts from being executed, since you are redirecting to another page instead of simply executing the ajax response.

Categories