brianclements brianclements - 1 month ago 9
Android Question

How to call existing javascript function from android activity

I'm building an ebook reader, so I have a webview that is loading a page stored locally on the device which it retrieved from the ebook itself. On this page, it has a javascript function

controls.nextPage()
that loads and runs just fine; it's used to not actually navigate to new web pages, but instead redraw virtual pages using javascript. I have this function bound to a button on the web page itself so that I can manually click it to test, again, works just fine when I touch the button on my webview.

Now I am trying to trigger this exact function from within my app. Ideally, I want to do this from a gesture swipe but that is too complicated for this specific question, as I have other issues with the gestures I need to solve first. For now, I've set up a button in my navigation drawer to trigger it and test it:

NavigationView navigationViewRight = (NavigationView) findViewById(R.id.nav_view_webview_right);
navigationViewRight.setNavigationItemSelectedListener(
new NavigationView.OnNavigationItemSelectedListener() {
@SuppressWarnings("StatementWithEmptyBody")
@Override
public boolean onNavigationItemSelected(MenuItem item) {
// Handle navigation view item clicks here.
int id = item.getItemId();

if (id == R.id.nav_camera) {
// *** - Focus here - *** //
runOnUiThread(new Runnable() {
@Override
public void run() {
mWebview.evaluateJavascript("controls.pageNext()", null);
}
});
} else if (id == R.id.nav_share) {
}

drawer.closeDrawer(GravityCompat.END);
return true;
}
}
);


Note, I've also tried calling
window.controls.pageNext()
to no avail.

So my page is loaded, and I've hit my in-page button to test the function; works. I go to hit my navigation drawer button in the app? Error (when using
window.controls.pageNext()
:

[INFO:CONSOLE(1)] "Uncaught TypeError: Cannot read property 'pageNext' of undefined", source: (1)


So it seems to be that
evaluateJavascript()
is being run in a fresh environment/thread. How can I tell it not to?

To get around this,I've tried to create an empty javascript interface in the hopes that I could simply initialize my page javascript into it and thus be able to call it from Android.

mWebview.addJavascriptInterface(new TestInterface(), "TestInterface");

public class TestInterface {
@JavascriptInterface
public void test() {
runOnUiThread(new Runnable() {
@Override
public void run() {
mWebview.evaluateJavascript("console.log('test')", null);
}
});
}
}


From my webapp, the javascript can call the interface just fine. calling
TestInterface.test();
yields:

[INFO:CONSOLE(1)] "test", source: (1)


But when I tried to assign a new function to that interface from my webapp:

TestInterface.testTwo = function() {
console.log('testTwo');
};
TestInterface.testTwo();


Android wouldn't have it:

[INFO:CONSOLE(674)] "testTwo", source: http://127.0.0.1:8080/js/main.js (674)


What's weird is that it's not really giving me much info to go on. I do know that the rest of my page has issues loading after the
testTwo()
attempt that
test()
did not, so I'm assuming failure to load the rest of the script.

Lastly, out of curiousity, I changed my navigation drawer button to try and run the new function like this:

mWebview.evaluateJavascript("TestInterface.testTwo()", null);


Log:

[INFO:CONSOLE(1)] "Uncaught TypeError: TestInterface.testTwo is not a function", source: (1)


Yes but is it something else? I dunno. Thoughts? Thank you.

Answer

So I figured out in the end what my issue was. By running mWebview.evaluateJavascript("window.location", null); I realized that I in fact was not actually on the page I thought I was. My ebook page was being loaded into an iframe or some other type of skeleton/wrapper in such a way that the webapp functions were not in fact available when running evaluateJavascript().

So once figured that out, I can confirm some things that I originally questioned above:

So it seems to be that evaluateJavascript() is being run in a fresh environment/thread. How can I tell it not to?

It does not. Just make sure you know what page is actually loaded.

To get around this,I've tried to create an empty javascript interface in the hopes that I could simply initialize my page javascript into it and thus be able to call it from Android.

This does in fact work. I'm not sure what my mistake was before, but if I create a javascript interface in Android, it's functions are available to the webapp AND I can in fact write new objects to the interface object from within the webapp. So, initializing new objects into TestInterface from within the webapp can be run within the Android app via: mWebview.evaluateJavascript("TestInterface.someNewFunctionFromWebapp()", null);

I can NOT however overwrite any existing objects/properties of that javascript interface object. so TestInterface.test() is immutable.

Comments