Restoring Android WebView page with ViewModel after device rotation / split view - javascript

I use WebView to show data, the data gets augmented via JS function. It works fine until the device gets rotated. Using ViewModel to keep the page data and restore it after a config change seemed to be the right idea but I ran into problems, the page didn't get restored. I pared down my code to a minimalist example, included here (sans the XML layout)
DataStore class to store the data, overriding ViewModel, simply store some strings in a list
package com.automationce.labyrinth.webview;
import android.arch.lifecycle.ViewModel;
import java.util.ArrayList;
import java.util.List;
/* The idea is to use ViewModel to store page data
* so it can be restored on device rotate/split view */
public final class DataStore extends ViewModel {
public List<String> records = new ArrayList<>();
public DataStore() {
super();
}
}
Simple web view class, overriding WebView
package com.automationce.labyrinth.webview;
import android.content.Context;
import android.graphics.Color;
import android.util.AttributeSet;
import android.webkit.WebView;
public final class SimpleWebView extends WebView {
// Use to keep and restore data between config changes
// (not related to browser history by any means)
private DataStore dataStore;
public SimpleWebView(Context context, AttributeSet attrs) {
super(context, attrs);
setBackgroundColor(Color.LTGRAY);
getSettings().setJavaScriptEnabled(true);
setWebContentsDebuggingEnabled(true);
// Define style, write JS function(s)
String content = "<!DOCTYPE html><html><head>" +
"<style>" +
"span { font-size: " +
0.875 +
"em; } " +
"</style>" +
"</head><body id=\"body\">" +
jsFunctions() + "</body></html>";
loadDataWithBaseURL(null, content, "text/html", "utf-8", null);
}
// JS function(s) as written to the page
private String jsFunctions() {
return "<script>" +
"function append (string) { " +
"var body = document.getElementById('body');" +
"var block = document.createElement('span');" +
"var text = document.createTextNode(string);" +
"var br = document.createElement('br'); " +
"block.appendChild(text);" +
"block.appendChild(br); " +
"body.appendChild(block);" +
"block.scrollIntoView();" +
" }" +
"</script>";
}
public void setHistory (final DataStore storedData) {
dataStore = storedData;
}
// Restore page from DataStore data
public void restore() {
for(String record : dataStore.records) {
evaluateJavascript("append ('" + record + "');", null);
}
}
// Append new data to existing page
public void append(final String string) {
dataStore.records.add(string);
evaluateJavascript("append ('" + string + "');", null);
}
}
And finally the MainActivity itself
package com.automationce.labyrinth.webview;
import android.arch.lifecycle.ViewModelProviders;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
public class MainActivity extends AppCompatActivity {
private SimpleWebView pageView;
private Button appendButton;
private int counter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DataStore storedData = ViewModelProviders.of(this).get(DataStore.class);
appendButton = findViewById(R.id.button);
pageView = findViewById(R.id.webView);
pageView.setHistory(storedData);
pageView.restore();
appendButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
append();
}
});
}
private void append() {
counter++;
pageView.append("Some kind of new text " + counter);
}
}
The idea was to preserve the data in ViewModel and restore it during creation. It took me a while to figure why it doesn't work. DataStore does get preserved during rotation (I can see all the history data in the DataStore object) but although I see the restore code being executed, it never gets rendered in SimpleWebView.
The reason is that while JavaScript function does get correctly written to the page, I am calling restore() from MainActivity.OnCreate before the page actually renders. The JS function gets called and quickly ignored because the page, in reality, doesn't exist as far as WebView is concerned (I think). Surprisingly, this actually worked on the majority of simulators I use - now I think it shouldn't and I have no clue why it did - until I ran it on a physical device (Galaxy S7), where the restore was ignored
My solution was to give my SimpleWebView a new WebViewClient and override its OnPageFinished method and in it restore the content of the page after the page finishes loading. SimpleView constructor ('firstView' boolean flag in the code below gets invalidated after the view reloads for the first time after the device rotation):
public SimpleWebView(Context context, AttributeSet attrs) {
super(context, attrs);
setBackgroundColor(Color.LTGRAY);
getSettings().setJavaScriptEnabled(true);
setWebContentsDebuggingEnabled(true);
setWebViewClient(new WebViewClient() {
#Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
Log.v("PAGE", "finished");
if (firstView) {
((SimpleWebView)view).restore();
}
}
});
.
.
The web page now gets reconstructed from the persistent data after device rotation/split window. It seems to work well, without any noticeable load delay (I am dealing with relatively small data set now).
I have looked for solutions, I really don't want to save and restore a String content of the whole page in OnSaveInstanceState / onRestoreInstanceState because in this case, it won't do me any good - I'll have the data but I still need to restore it after the page gets rendered
Words of wise I seek from the collective:
Is my analysis correct?
Are there better ways of handling this?
Is there something (an unpleasant production code surprise, etc. - I am pretty new to Android) for which I should be watching out?
Thanks, Jiri

Related

Get next 300 chars starting from starting keyword in android using Js interface

I have a webview app where i need to select random text from web page loaded in webview , i have the java interface which copies the text to the clipboard , what i want now is somehow to automatically get the the js interace to copy the next 300 chars starting from that selected word , is there anyway to do that using js interface and thank you in advance .
This is my code
copy.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
view = xview;
xview.evaluateJavascript("(function(){return window.getSelection().toString()})()", new ValueCallback<String>() {
#Override public void onReceiveValue(String value) {
ClipData clip = ClipData.newPlainText("label", value);
clipboard.setPrimaryClip(clip);
}
});
popmenu.dismiss();
}
});

Android WebView not returning desired HTML

so a quick overview of what I'm doing
I am using Android Webview to Render JavaScript and then reading the HTML from the javascript to parse it.
I am currently having trouble with retrieving the HTML from a website called Sport Chek.
Here is the code for my SportChekSearch class:
public class SportChekSearch extends SearchQuery{
public Elements finalDoc;
private ArrayList<Item> processed;
private final Handler uiHandler = new Handler();
public int status = 0;
//This basically is just so that the class knows which Activity we're working with
private Context c;
protected class JSHtmlInterface {
#android.webkit.JavascriptInterface
public void showHTML(String html) {
final String htmlContent = html;
uiHandler.post(
new Runnable() {
#Override
public void run() {
Document doc = Jsoup.parse(htmlContent);
}
}
);
}
}
/**
* Constructor method
* #param context The context taken from the webview (So that the asynctask can show progress)
*/
public SportChekSearch(Context context, String query) {
final Context c = context;
try {
final WebView browser = new WebView(c);
browser.setVisibility(View.INVISIBLE);
browser.setLayerType(View.LAYER_TYPE_NONE, null);
browser.getSettings().setJavaScriptEnabled(true);
browser.getSettings().setBlockNetworkImage(true);
browser.getSettings().setDomStorageEnabled(true);
browser.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
browser.getSettings().setLoadsImagesAutomatically(false);
browser.getSettings().setGeolocationEnabled(false);
browser.getSettings().setSupportZoom(false);
browser.getSettings().setUserAgentString("Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36");
browser.addJavascriptInterface(new JSHtmlInterface(), "JSBridge");
browser.setWebViewClient(
new WebViewClient() {
#Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
}
#Override
public void onPageFinished(WebView view, String url) {
browser.loadUrl("javascript:window.JSBridge.showHTML('<html>'+document.getElementsByTagName('html')[0].innerHTML+'</html>');");
}
}
);
browser.loadUrl("https://www.sportchek.ca/search.html#q=" + query.replaceAll(" ", "+") + "&lastVisibleProductNumber=3");
browser.loadUrl(browser.getUrl());
final String link = browser.getUrl();
new fetcher(c).execute(link);
}
catch(Exception e){
e.printStackTrace();
}
//Get the link from the WebView, and save it in a final string so it can be accessed from worker thread
}
/**
* This subclass is a worker thread meaning it does work in the background while the user interface is doing something else
* This is done to prevent "lag".
* To call this class you must write fetcher(Context c).execute(The link you want to connect to)
*
*/
class fetcher extends AsyncTask<String, Void, Elements> {
Context mContext;
ProgressDialog pdialog;
public fetcher(Context context) {
mContext = context;
}
#Override
protected void onPreExecute() {
super.onPreExecute();
pdialog = new ProgressDialog(mContext);
pdialog.setTitle(R.string.finding_results);
pdialog.setCancelable(false);
pdialog.show();
}
//This return elements because the postExecute() method needs an Elements object to parse its results
#Override
protected Elements doInBackground(String... strings) {
//You can pass in multiple strings, so this line just says to use the first string
String link = strings[0];
//For Debug Purposes, Do NOT Remove - **Important**
System.out.println("Connecting to: " + link);
try {
doc = Jsoup.connect(link)
.ignoreContentType(true)
.userAgent("Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36")
.timeout(10000)
.get();
finalDoc = doc.select("body section.product-grid-wrapper");
System.out.println(finalDoc.toString());
} catch (IOException e) {
e.printStackTrace();
}
return finalDoc;
}
#Override
protected void onPostExecute(Elements result) {
//This line clears the list of info in the Search activity
//I should probably be using a getter method but adapter is a static variable so it shouldn't matter
//parse seperates document into elements
//crunch results formats those elements into item objects
//I am saving the result of this to an ArrayList<Item> called "processed"
processed = crunchResults(result);
//For debug purposes, do NOT remove - **Important**
System.out.println(processed.size() + " results have been crunched by Sport Chek.");
//Adds all of the processed results to the list of info in Search activity
ClothingSearch.adapter.addAll(processed);
//For debug purposes, do NOt remove - **Important
System.out.println("Adapter has been notified by Sport Chek.");
//Closes the progress dialog called pdialog assigned to the AsyncTask
pdialog.dismiss();
ClothingSearch.adapter.notifyDataSetChanged();
SearchQueueHandler.makeRequest(mContext, processed, SearchQueueHandler.CLOTHING_SEARCH);
}
}
public ArrayList<Item> crunchResults(Elements e){
ArrayList<Item> results = new ArrayList<Item>();
try {
for (int i = 0; i < e.size(); i++) {
Element ele = e.get(i);
String link = "https://www.sportchek.ca" + ele.select(" a.product-grid__link").attr("href");
System.out.println("https://www.sportchek.ca" + ele.select(" a.product-grid__link").attr("href"));
String title = ele.select(" span.product-title-text").text();
String pricestring = ele.select(" span.product-price__wrap").text();
price = Double.parseDouble(pricestring.substring(pricestring.lastIndexOf("$")));
System.out.println(pricestring);
//*******************************************
String store = "Sport Chek";
//Adds the formatted item to an ArrayList of items
results.add(new Item(title, store, price, link));
//Prints the object's to String to console
//For debug purposes, do NOT remove - **Important
System.out.println(results.get(i).toString());
}
} catch (Exception a){
a.printStackTrace();
}
return results;
}
public int getStatus(){
return status;
}
}
The two relevant methods are doInBackground in my AsyncTask and the crunchResults method.
Here is the result I get from using Ctrl+Shift+I on the actual website (Desired Result):
But when running the above code and using a println here is the result that I get for the tag section class="product-grid-wrapper" :
<section class="product-grid-wrapper">
<ul data-module-type="SearchProductGrid" class="product-grid__list product-grid__list_quickview">
<!-- #product-grid__item-template -->
</ul>
</section>
Can anyone help me figure out why I am not getting my desired result?
All help is appreciated
EDIT: for this specific search that the println data was collected from, the link was https://www.sportchek.ca/search.html#q=men+coat&lastVisibleProductNumber=3
It looks like what you are actually getting is the actual html sent by the server, and that your 'desired result' is what the DOM looks like after the JavaScript runs.
Your 'actual' is what I see if I use "View Source" in Chrome, while your "desired result" is what I see if I use Chrome's DOM inspector.
On further inspection, I see that you are not actually getting the HTML from the browser, you are (indirectly) using JSoup's Connection object to get the HTML directly. Unfortunately, that's not going to run the Javascript.
Instead, you're going to have to get the HTML from the WebView after the JavaScript runs. For a possible way to do that, see How do I get the web page contents from a WebView?
Then, you give the HTML that you get from that to JSoup with
Jsoup.parse(html);

Wicket AbstractDefaultAjaxBehavior do recursive update the page

I have some ajax Behaviour that should pick some data using JS, and turn it back to Java. Sometimes it works but quite ofen it is just add url parameter and do page refresing/
public abstract class LoggedVKIdBehaviour extends AbstractDefaultAjaxBehavior {
private static final Logger logger = LoggerFactory.getLogger(LoggedVKIdBehaviour.class);
#Override
protected void respond(AjaxRequestTarget target) {
String loggedVkId = RequestCycle.get().getRequest().getRequestParameters().getParameterValue("logged_vkid").toString();
logger.info("ajax has comming with logged VK ID " + loggedVkId);
recived(target, loggedVkId);
}
protected abstract void recived(AjaxRequestTarget target, String loggedVkId);
#Override
public void renderHead(final Component component, IHeaderResponse response) {
super.renderHead(component, response);
Map<String, Object> map = new HashMap<>();
map.put("callbackFunction", getCallbackFunction(CallbackParameter.explicit("logged_vkid")));
//
PackageTextTemplate ptt = new PackageTextTemplate(LoggedVKIdBehaviour.class, "vkid_callback.js");
OnDomReadyHeaderItem onDomReadyHeaderItem = OnDomReadyHeaderItem.forScript(ptt.asString(map));
response.render(onDomReadyHeaderItem);
}
}
js template
var calback = ${callbackFunction};
var logged_vk_id = 11;
function authInfo(response) {
if (response.session) {
logged_vk_id = response.session.mid;
calback(response.session.mid);
console.log("recived callback from VK " + logged_vk_id);
}
}
$(document).ready(function () {
VK.Auth.getLoginStatus(authInfo);
});
it is do recursive redirection like http://localhost:8080/mytool/product/1?logged_vkid=332797331&logged_vkid=332797331&logged_vkid=332797331&logged_vkid=332797331&logged_vkid=332773...
As i understand Ajaj technology - iti asynchronus requests, that shouldn't touch main url at all. So what is the reason for page refreshing?
this is generated Callback function
function (logged_vkid) {
var attrs = {"u":"../wicket/bookmarkable/com.tac.kulik.pages.product.ProductPage?12-1.IBehaviorListener.0-&productID=1"};
var params = [{"name":"logged_vkid","value":logged_vkid}];
attrs.ep = params.concat(attrs.ep || []);
Wicket.Ajax.ajax(attrs);
}
I use wicket 7.2
I did a lot investigations for few days. And found that when i remove
setPageManagerProvider(new NoSerializationPageManagerProvider(this));
Application throw me exepton in polite logs
org.apache.wicket.WicketRuntimeException: A problem occurred while
trying to collect debug information about not serializable object look
like it is could come from aused by: java.io.NotSerializableException:
com.tac.kulik.panel.smaccounts.SMAccountsPanel$1
which means that page tryed to be serialized for SOME REASON but $1 it is mean Anonimous class. I had few class created anonimously to ges some ajax links coming from ListView to be managed on parent panel. So After removing this Anonimous class logic, everything start and run well.
So i am happy, but still don't understand which reason page did serialization after ajax, and what the reason was to refresh whole page.

Geolocation with PhoneGap on Android

I work on an application that using PhoneGap. For the moment, I only test it on Android. I have several pages in my application that need the geolocation feature.
So I made a JS to handle it with this line of code (of course it's not my unique line of code) :
navigator.geolocation.watchPosition(successGeolocation, errorGeolocation, {maximumAge: 5000, enableHighAccuracy: true});
The geolocation need to be the most accurate possible so I use the GPS and it can take some time to have a GPS Fix.
The problem is when the user navigates from one page to another. The WatchPosition stop (it's normal because the user load an other page) and when I recall it the GPS need to Fix again.
It's very annoying and I search a solution to keep the GPS active. Someone has an idea for me ? Maybe with a plugin or a native Android LoC I can keep it active during all the application life ?
Thanks.
First step you should create an Android plugin that will give you API for receiving the location data.
Creating a plugin is quite easy and is explained here:
Plugin Development Guide and Developing a Plugin on Android.
You can also see an example of creating a Cordova plugin here.
Next, create a location monitor class. You can make it singleton and initialize it from your main activity.
Here is a simple, but working code I compiled from several sources and many tests to fit my needs.
The main code is taken from here, though I simplified it as much as was possible.
public class LocationMonitor
{
private LocationListener locationListener = null;
private LocationManager locationManager = null;
private Location location = null;
public LocationMonitor()
{
}
public void startGPSActivity(Context context)
{
LocationLooper looper = new LocationLooper();
looper.start();
while (!looper.isReady())
{
}
looper.handler.post(new LocationBootstrapper(context));
}
public void stopGPSActivity()
{
locationManager.removeUpdates(locationListener);
}
public Location getLocation()
{
return location;
}
private class LocationLooper extends Thread
{
private Handler handler;
private LocationLooper()
{
}
public void run()
{
Looper.prepare();
this.handler = new Handler();
Looper.loop();
}
public boolean isReady()
{
return this.handler != null;
}
}
private class LocationBootstrapper implements Runnable
{
private Context context;
private LocationBootstrapper(Context context)
{
this.context = context;
}
public void run()
{
locationListener = new LocationListenerImpl();
locationManager = (LocationManager)context.getSystemService(Context.LOCATION_SERVICE);
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 1, locationListener);
}
}
private class LocationListenerImpl implements LocationListener
{
private LocationListenerImpl()
{
}
#Override
public void onLocationChanged(Location location)
{
LocationMonitor.this.location = location;
Log.i("LocationMonitor", "New location: lat= " + location.getLatitude() + " lng=" + location.getLongitude() + " acc=" + location.getAccuracy());
}
#Override
public void onProviderDisabled(String provider)
{
}
#Override
public void onProviderEnabled(String provider)
{
}
#Override
public void onStatusChanged(String provider, int status, Bundle extras)
{
}
}
}
Now access the LocationMonitor class from your plugin and you have your desired solution - page changes will not re-initialize your GPS and location data is available to your PhoneGap app.
Cheers

Android Calling JavaScript functions in WebView

I am trying to call some javascript functions sitting in an html page running inside an android webview. Pretty simple what the code tries to do below - from the android app, call a javascript function with a test message, which inturn calls a java function back in the android app that displays test message via toast.
The javascript function looks like:
function testEcho(message){
window.JSInterface.doEchoTest(message);
}
From the WebView, I have tried calling the javascript the following ways with no luck:
myWebView.loadUrl("javascript:testEcho(Hello World!)");
mWebView.loadUrl("javascript:(function () { " + "testEcho(Hello World!);" + "})()");
I did enable javascript on the WebView
myWebView.getSettings().setJavaScriptEnabled(true);
// register class containing methods to be exposed to JavaScript
myWebView.addJavascriptInterface(myJSInterface, "JSInterface");
And heres the Java Class
public class JSInterface{
private WebView mAppView;
public JSInterface (WebView appView) {
this.mAppView = appView;
}
public void doEchoTest(String echo){
Toast toast = Toast.makeText(mAppView.getContext(), echo, Toast.LENGTH_SHORT);
toast.show();
}
}
I've spent a lot of time googling around to see what I may be doing wrong. All examples I have found use this approach. Does anyone see something wrong here?
Edit: There are several other external javascript files being referenced & used in the html, could they be the issue?
I figured out what the issue was : missing quotes in the testEcho() parameter. This is how I got the call to work:
myWebView.loadUrl("javascript:testEcho('Hello World!')");
From kitkat onwards use evaluateJavascript method instead loadUrl to call the javascript functions like below
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
webView.evaluateJavascript("enable();", null);
} else {
webView.loadUrl("javascript:enable();");
}
public void run(final String scriptSrc) {
webView.post(new Runnable() {
#Override
public void run() {
webView.loadUrl("javascript:" + scriptSrc);
}
});
}
I created a nice wrapper to call JavaScript methods; it also shows JavaScript errors in log:
private void callJavaScript(String methodName, Object...params){
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("javascript:try{");
stringBuilder.append(methodName);
stringBuilder.append("(");
for (int i = 0; i < params.length; i++) {
Object param = params[i];
if(param instanceof String){
stringBuilder.append("'");
stringBuilder.append(param.toString().replace("'", "\\'"));
stringBuilder.append("'");
}
if(i < params.length - 1){
stringBuilder.append(",");
}
}
stringBuilder.append(")}catch(error){Android.onError(error.message);}");
webView.loadUrl(stringBuilder.toString());
}
You need to add this too:
private class WebViewInterface{
#JavascriptInterface
public void onError(String error){
throw new Error(error);
}
}
And add this interface to your webview:
webView.getSettings().setJavaScriptEnabled(true);
webView.addJavascriptInterface(new WebViewInterface(), "AndroidErrorReporter");
Yes you have the syntax error. If you want to get your Javascript errors and printing statements in your logcat you must implement the onConsoleMessage(ConsoleMessage cm) method in your WebChromeClient. It gives the complete stack traces like Web console(Inspect element). Here is the method.
public boolean onConsoleMessage(ConsoleMessage cm)
{
Log.d("Message", cm.message() + " -- From line "
+ cm.lineNumber() + " of "
+ cm.sourceId() );
return true;
}
After implementation you will get your Javascript errors and print statements (console.log) on your logcat.
Modification of #Ilya_Gazman answer
private void callJavaScript(WebView view, String methodName, Object...params){
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("javascript:try{");
stringBuilder.append(methodName);
stringBuilder.append("(");
String separator = "";
for (Object param : params) {
stringBuilder.append(separator);
separator = ",";
if(param instanceof String){
stringBuilder.append("'");
}
stringBuilder.append(param.toString().replace("'", "\\'"));
if(param instanceof String){
stringBuilder.append("'");
}
}
stringBuilder.append(")}catch(error){console.error(error.message);}");
final String call = stringBuilder.toString();
Log.i(TAG, "callJavaScript: call="+call);
view.loadUrl(call);
}
will correctly create JS calls e.g.
callJavaScript(mBrowser, "alert", "abc", "def");
//javascript:try{alert('abc','def')}catch(error){console.error(error.message);}
callJavaScript(mBrowser, "alert", 1, true, "abc");
//javascript:try{alert(1,true,'abc')}catch(error){console.error(error.message);}
Note that objects will not be passed correctly - but you can serialize them before passing as an argument.
Also I've changed where the error goes, I've diverted it to the console log which can be listened by:
webView.setWebChromeClient(new CustomWebChromeClient());
and client
class CustomWebChromeClient extends WebChromeClient {
private static final String TAG = "CustomWebChromeClient";
#Override
public boolean onConsoleMessage(ConsoleMessage cm) {
Log.d(TAG, String.format("%s # %d: %s", cm.message(),
cm.lineNumber(), cm.sourceId()));
return true;
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<WebView
android:id="#+id/webView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</RelativeLayout>
MainActivity.java
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import com.bluapp.androidview.R;
public class WebViewActivity3 extends AppCompatActivity {
private WebView webView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_web_view3);
webView = (WebView) findViewById(R.id.webView);
webView.setWebViewClient(new WebViewClient());
webView.getSettings().setJavaScriptEnabled(true);
webView.loadUrl("file:///android_asset/webview1.html");
webView.setWebViewClient(new WebViewClient(){
public void onPageFinished(WebView view, String weburl){
webView.loadUrl("javascript:testEcho('Javascript function in webview')");
}
});
}
}
assets file
<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.0//EN" "http://www.wapforum.org/DTD/xhtml-mobile10.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><title>WebView1</title>
<meta forua="true" http-equiv="Cache-Control" content="max-age=0"/>
</head>
<body style="background-color:#212121">
<script type="text/javascript">
function testEcho(p1){
document.write(p1);
}
</script>
</body>
</html>
Here is an example to load js script from the asset on WebView.
Put script to a file will help reading easier
I load the script in onPageFinished because I need to access some DOM element inside the script (to able to access it should be loaded or it will be null). Depend on the purpose of the script, we may load it earlier
assets/myjsfile.js
document.getElementById("abc").innerText = "def"
document.getElementById("abc").onclick = function() {
document.getElementById("abc").innerText = "abc"
}
WebViewActivity
webView.settings.javaScriptEnabled = true
webView.webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView?, url: String?) {
super.onPageFinished(view, url)
val script = readTextFromAsset("myjsfile.js")
view.loadUrl("javascript: $script")
}
}
fun readTextFromAsset(context: Context, fileName: String): String {
return context.assets.open(fileName).bufferedReader().use { it.readText()
}

Categories