Issue with loading local javascript files inside a webview - android

I had with an issue that has plagued me for days. It turned out that it was an Android glitch and has been submitted, confirmed, and hopefully will be fixed in a future release. Now I have found a solution that works for me, and will provide it below, however the solution is not perfect as it involves editing the phone gap source. Mainly my question is if someone can find a better solution to this issue.
The Bug:
There is a glitch when you attempt to load a page inside of a WebView on Android 3.0+. The glitch is that if that page references any local javascript files, you cannot append query data to the url. Basically
This works:
<script type="text/javascript" src="StaticJS.js"></script>
This does not work:
<script type="text/javascript" src="StaticJS.js?var=val"></script>
Why the hell would anyone want to do this since the file obviously can't do anything with the query vals? Well for me I have a phonegap application that loads a settings file via JSONP, however if a settings file is not specified it defaults to a local file. So yeah, the file can't process the query data but it would be nice to use the same file format and loading structure.
Solution 1 (Non-PhoneGap)
So there is an easy solution to this if the target android platform is 11(Honeycomb) or higher. (As long as you are careful and do not use any method that do not exists in any lower API levels this code will run on <11 apis, but you will still have to set 11 as your target)
Basically you add a WebViewClient to the WebView that utilizes the shouldInterceptRequest method to intercept the loading of local js files with query data attached.
import java.io.IOException;
import java.io.InputStream;
import android.content.res.AssetManager;
import android.webkit.WebResourceResponse;
import android.webkit.WebView;
import android.webkit.WebViewClient;
public class PatchingWebViewClient extends WebViewClient{
AssetManager am;
public PatchingWebViewClient(AssetManager am){
this.am = am;
}
#Override
public WebResourceResponse shouldInterceptRequest (WebView view, String url){
if(url.indexOf("file:///android_asset") == 0 && url.contains("?")){
String filePath = url.substring(22, url.length());
filePath = filePath.substring(0, filePath.indexOf("?"));
try {
InputStream is = am.open(filePath);
WebResourceResponse wr = new WebResourceResponse("text/javascript", "UTF-8", is);
return wr;
} catch (IOException e) {
return null;
}
}else{
return null;
}
}
}
To set the WebViewClient, your code would look something like this:
import android.app.Activity;
import android.os.Bundle;
import android.webkit.WebView;
public class CanWeBreakAWebViewActivity extends Activity {
WebView mWebView;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mWebView = (WebView) findViewById(R.id.webview);
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.setWebViewClient(new PatchingWebViewClient(this.getAssets()));
mWebView.loadUrl("file:///android_asset/index.html");
}
}
Solution 2 (PhoneGap)
Now for Phonegap I don't have a clean solution. My solution is to go and download the Phonegap source and edit the CordovaWebViewClient by adding the following method:
#Override
public WebResourceResponse shouldInterceptRequest (WebView view, String url){
if(url.indexOf("file:///android_asset") == 0 && url.contains("?")){
String filePath = url.substring(22, url.length());
filePath = filePath.substring(0, filePath.indexOf("?"));
try {
InputStream is = ctx.getAssets().open(filePath);
WebResourceResponse wr = new WebResourceResponse("text/javascript", "Cp1252", is);
return wr;
} catch (IOException e) {
return null;
}
}else{
return null;
}
}
Solution 3 (Non-Existent)
This solution would hopefully be some easy to include class or tweak to the main activity so that you can use phone gap but could just use a .jar file of the code, making upgrades easier.

Thanks for this post, you clued me in to the latest solution which is simply to use IceCreamCordovaWebViewClient.
#Override
public void init() {
super.init(webView, new IceCreamCordovaWebViewClient(this, webView), new CordovaChromeClient(this, webView));
}

Query data is appended to javascript files (and other file types like css) to prevent browser caching. The query data is useless to the file but the browser treats it as new because the location is changed (in the eyes of the browser) and it loads a fresh copy.
I'm glad you found an answer to your problem, just thought I'd give my input as to why people use this method.

Related

ReactImageView set Bitmap source

I have a rather strange situation where I want to display images in React-Native coming from a native library in bitmap form. In order to get the Bitmap object that needs to be rendered, the component needs to make use of an "adId" prop.
I thus far have the following code for a native component:
import android.graphics.Bitmap;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.react.uimanager.SimpleViewManager;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.views.image.ReactImageView;
public class AdIconViewManager extends SimpleViewManager<ReactImageView> {
public static final String REACT_CLASS = "RCTAdIconView";
#Override
public String getName() {
return REACT_CLASS;
}
#Override
public ReactImageView createViewInstance(ThemedReactContext context) {
return new ReactImageView(context, Fresco.newDraweeControllerBuilder(), null, null);
}
#ReactProp(name = "adId")
public void setAdId(ReactImageView view, String adId) {
final Bitmap icon = AppodealModule.getIcon(adId);
// What now?
}
}
My problem is that I cannot figure out how to set the bitmap source of the ReactImageView. ReactImageView is completely undocumented but seems to use Fresco (which I don't know at all). Is there some way I can use some other DraweeControllerBuilder to provide Bitmaps?
Fresco manages the bitmap cache itself thus it might be more convenient to use getIconUrl and set an URL instead of bitmap, using this guide - http://frescolib.org/docs/using-simpledraweeview.html
If you want to use bitmap itself these docs can give you a vision of the fresco pipeline - http://frescolib.org/docs/using-image-pipeline.html

Android: MobileFirst sending data from Native to cross page

My Task is as follows: using IBM MobileFirst create a Hybrid app and implement a JS calculator. show date retrieved from native java APIs to the web page.
My attempts:
I followed Documentations here and implemented the whole Native code onCreate method
I found this answer"the first one" illustrating that i should use it on onInitWebFrameworkComplete,
Solution provided didn't work
I am working with MobileFirst version 7
full sample code is provided
Suggestion: should i create the whole action bar in native code then merge it in the cross ui, is that available? I only need to send a petite string of date
I am not clear on your attempts, so here is a quick demonstration how to click a button in HTML and trigger the Send Action API to get the current Date in Java and return it to JavaScript, and then display it.
index.html
<button onclick="getDateFromJava();">show current date from Java</button>
main.js
function wlCommonInit(){
WL.App.addActionReceiver ("returneDdateFromJava", returnedDateFromJava);
}
function getDateFromJava() {
WL.App.sendActionToNative("retrieveDate");
}
function returnedDateFromJava(received){
if (received.action === "returnedDateFromJava"){
alert (JSON.stringify(received));
}
}
main Java class file
Find onInitWebFrameworkComplete
Add an ActionReceiver after the else:
import com.worklight.androidgap.api.WLActionReceiver;
...
...
public void onInitWebFrameworkComplete(WLInitWebFrameworkResult result){
if (result.getStatusCode() == WLInitWebFrameworkResult.SUCCESS) {
super.loadUrl(WL.getInstance().getMainHtmlFilePath());
} else {
handleWebFrameworkInitFailure(result);
}
ActionReceiver ActionReceiver = new ActionReceiver();
WL.getInstance().addActionReceiver(ActionReceiver);
}
ActionReceiver class
package com.getDateApp;
import java.util.Date;
import org.json.JSONException;
import org.json.JSONObject;
import com.worklight.androidgap.api.WL;
import com.worklight.androidgap.api.WLActionReceiver;
public class ActionReceiver implements WLActionReceiver{
public void onActionReceived(String action, JSONObject data){
if (action.equals("retrieveDate")){
Date date = new Date();
JSONObject returnedDate = new JSONObject();
try {
returnedDate.put("dateFromJava", date);
} catch (JSONException e) {
e.printStackTrace();
}
WL.getInstance().sendActionToJS("returnedDateFromJava", returnedDate);
}
}
}

Two way sync for cookies between HttpURLConnection (java.net.CookieManager) and WebView (android.webkit.CookieManager)

Unfortunately, there's a multitude of cookie managers for Android. The cookies for HttpURLConnection are maintained by java.net.CookieManager and the cookies for WebView are maintained by android.webkit.CookieManager. These cookie repositories are separate and require manual synchronization.
My app uses both HttpURLConnections and shows WebViews (it's a native-HTML hybrid). Naturally, I want both to share all cookies - so I will have a transparent session all across.
More Specifically:
When a cookie is set/changed in an HttpURLConnection, I want the WebViews to see this change as well.
When a cookie is set/changed in a WebView, I want the next HttpURLConnections to see this change as well.
Simply put - I'm looking for a two-way sync. Or even better, to have them both use the same cookie repository. You can assume both are active in the same time (like on different tabs).
Questions:
Is there a way to make both use the same cookie repository?
If not, what is the recommended practice to do the manual sync? When exactly should I sync and how?
Related Question: This question tackles a similar issue, but only implements one-way sync (HttpURLConnection -> WebView).
My Best Idea So Far: I really want to avoid a manual sync, so I tried to think how to make both use the same repository. Maybe I can create my own core handler which extends java.net.CookieManager. I will set it as the core cookie handler using java.net.CookieHandler.setDefault(). Its implementation will be a proxy to the android.webkit.CookieManager handler instance (for every function I'll simply access the webkit manager).
I've implemented my own idea. It's actually pretty cool. I've created my own implementation of java.net.CookieManager which forwards all requests to the WebViews' webkit android.webkit.CookieManager. This means no sync is required and HttpURLConnection uses the same cookie storage as the WebViews.
Class WebkitCookieManagerProxy:
import java.io.IOException;
import java.net.CookieManager;
import java.net.CookiePolicy;
import java.net.CookieStore;
import java.net.URI;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
public class WebkitCookieManagerProxy extends CookieManager
{
private android.webkit.CookieManager webkitCookieManager;
public WebkitCookieManagerProxy()
{
this(null, null);
}
public WebkitCookieManagerProxy(CookieStore store, CookiePolicy cookiePolicy)
{
super(null, cookiePolicy);
this.webkitCookieManager = android.webkit.CookieManager.getInstance();
}
#Override
public void put(URI uri, Map<String, List<String>> responseHeaders) throws IOException
{
// make sure our args are valid
if ((uri == null) || (responseHeaders == null)) return;
// save our url once
String url = uri.toString();
// go over the headers
for (String headerKey : responseHeaders.keySet())
{
// ignore headers which aren't cookie related
if ((headerKey == null) || !(headerKey.equalsIgnoreCase("Set-Cookie2") || headerKey.equalsIgnoreCase("Set-Cookie"))) continue;
// process each of the headers
for (String headerValue : responseHeaders.get(headerKey))
{
this.webkitCookieManager.setCookie(url, headerValue);
}
}
}
#Override
public Map<String, List<String>> get(URI uri, Map<String, List<String>> requestHeaders) throws IOException
{
// make sure our args are valid
if ((uri == null) || (requestHeaders == null)) throw new IllegalArgumentException("Argument is null");
// save our url once
String url = uri.toString();
// prepare our response
Map<String, List<String>> res = new java.util.HashMap<String, List<String>>();
// get the cookie
String cookie = this.webkitCookieManager.getCookie(url);
// return it
if (cookie != null) res.put("Cookie", Arrays.asList(cookie));
return res;
}
#Override
public CookieStore getCookieStore()
{
// we don't want anyone to work with this cookie store directly
throw new UnsupportedOperationException();
}
}
And use it by doing this on your application initialization:
android.webkit.CookieSyncManager.createInstance(appContext);
// unrelated, just make sure cookies are generally allowed
android.webkit.CookieManager.getInstance().setAcceptCookie(true);
// magic starts here
WebkitCookieManagerProxy coreCookieManager = new WebkitCookieManagerProxy(null, java.net.CookiePolicy.ACCEPT_ALL);
java.net.CookieHandler.setDefault(coreCookieManager);
Testing
My initial testing show this is working well. I see cookies shared between the WebViews and HttpURLConnection. I hope I'll not run into any issues. If you try this out and discover any problem, please comment.

accessing a file from folder within assets

I have an application which displays html file from assets folder.Now the issue is if there is only one html, all works fine but in case I have more than 1 html, how do I refer it?
Folder structure when only 1 html is present is like
and I refer to html file as follows:
InputStream input = this.getAssets().open("index.html");
but in case of multiple html's, it should/will be like
So, in this scenario , how do I refer different html's?i.e. how do I refer a file from a folder placed within assets folder?
As I have no idea how to proceed, any help appreciated...
You can access it as a URL like so:
"file:///android_asset/myfile.html"
So in a WebView you can use the method:
loadUrl("file:///android_asset/myfile.html")
You can refer like this
WebView webview;
webview=(WebView)findViewById(R.id.webView1);
webview.getSettings().setJavaScriptEnabled(true);
webview.loadUrl("file:///android_asset/HTML 1/index.html");
webview.addJavascriptInterface(new MyJavaScriptInterface(), "Android");
final class MyJavaScriptInterface
{
public void ProcessJavaScript(final String scriptname, final String args)
{
mHandler.post(new Runnable()
{
public void run()
{
//ToDo Something here...
}
});
}
}

Load HTML file into WebView

I have a local html page along with several other resources pointed by it (css files and Javascript libraries) that I would like to load into a WebView . How could this be achieved ?
Perhaps not the best way to procede but I'm still experimenting.
The easiest way would probably be to put your web resources into the assets folder then call:
webView.loadUrl("file:///android_asset/filename.html");
For Complete Communication between Java and Webview See This
Update: The assets folder is usually the following folder:
<project>/src/main/assets
This can be changed in the asset folder configuration setting in your <app>.iml file as:
<option name=”ASSETS_FOLDER_RELATIVE_PATH” value=”/src/main/assets” />
See Article Where to place the assets folder in Android Studio
probably this sample could help:
WebView lWebView = (WebView)findViewById(R.id.webView);
File lFile = new File(Environment.getExternalStorageDirectory() + "<FOLDER_PATH_TO_FILE>/<FILE_NAME>");
lWebView.loadUrl("file:///" + lFile.getAbsolutePath());
In this case, using WebView#loadDataWithBaseUrl() is better than WebView#loadUrl()!
webView.loadDataWithBaseURL(url,
data,
"text/html",
"utf-8",
null);
url: url/path String pointing to the directory all your JavaScript files and html links have their origin. If null, it's about:blank.
data: String containing your hmtl file, read with BufferedReader for example
More info: WebView.loadDataWithBaseURL(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String)
The Accepted Answer is not working for me, This is what works for me
WebSettings webSetting = webView.getSettings();
webSetting.setBuiltInZoomControls(true);
webView1.setWebViewClient(new WebViewClient());
webView.loadUrl("file:///android_asset/index.html");
XML Layout File:
<WebView android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/webView"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".activities.Bani9">
</WebView>
Java Code:
public class Bani9 extends AppCompatActivity {
WebView webView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_bani9);
webView = findViewById(R.id.webView);
WebSettings webSetting = webView.getSettings();
webSetting.setBuiltInZoomControls(true);
webView.setWebViewClient(new WebViewClient());
webView.loadUrl("file:///android_asset/punjabi/bani9.html");
}
}
Make sure you set file path accurately.
From the official guide https://developer.android.com/develop/ui/views/layout/webapps/load-local-content :
Store the HTML as an asset in app/src/main/assets/
Use WebViewAssetLoader to load the asset. Construct it in your onCreate() as follows:
final WebViewAssetLoader assetLoader = new WebViewAssetLoader.Builder()
.addPathHandler("/assets/", new WebViewAssetLoader.AssetsPathHandler(this))
.addPathHandler("/res/", new WebViewAssetLoader.ResourcesPathHandler(this))
.build();
Subclass WebViewClient to wrap WebViewAssetLoader:
private static class LocalContentWebViewClient extends WebViewClientCompat {
private final WebViewAssetLoader mAssetLoader;
LocalContentWebViewClient(WebViewAssetLoader assetLoader) {
mAssetLoader = assetLoader;
}
#Override
#RequiresApi(21)
public WebResourceResponse shouldInterceptRequest(WebView view,
WebResourceRequest request) {
return mAssetLoader.shouldInterceptRequest(request.getUrl());
}
#Override
#SuppressWarnings("deprecation") // to support API < 21
public WebResourceResponse shouldInterceptRequest(WebView view,
String url) {
return mAssetLoader.shouldInterceptRequest(Uri.parse(url));
}
}
This basically passes the request URL to WebViewAssetLoader to load web content from an asset.
Use assetLoader from (2) to construct WebViewClient from (3), and set it in your WebView. Your index.html can be loaded by using https and the default domain appassets.androidplatform.net:
mWebView.setWebViewClient(new LocalContentWebViewClient(assetLoader));
mWebView.loadUrl("https://appassets.androidplatform.net/assets/index.html");
Note that loading local files using web-like URLs instead of file:// is desirable as it is compatible with the Same-Origin policy.

Categories

Resources