I have an HTML Web page with a button that triggers a POST request when the user clicks on. When the request is done, the following code is fired:
window.open(fileUrl);
Everything works great in the browser, but when implement that inside of a Webview Component, the new tab doesn't is opened.
FYI: On my Android App, I have set the followings things:
webview.getSettings().setJavaScriptEnabled(true);
webview.getSettings().setSupportMultipleWindows(true);
webview.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);
On the AndroidManifest.xml
I have the following permissions:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_DOWNLOAD_MANAGER"/>
I try too with a setDownloadListener
to catch the download. Another approach was replaced the WebViewClient()
for WebChromeClient()
but the behavior was the same.
An SQL BLOB is a built-in type that stores a Binary Large Object as a column value in a row of a database table. By default drivers implement Blob using an SQL locator(BLOB) , which means that a Blob object contains a logical pointer to the SQL BLOB data rather than the data itself.
Explanation: WebViewClient can't load Blob URL. A work around would be to convert Blob URL to a Blob Object, then to a Base64 data on the web side. The native side will download the attachment in Base64 data according to the mime type specified in the prefix of the Base64 data.
Download Blob URL as Blob object * 2. Convert Blob object to Base64 data * 3. Pass Base64 data to Android layer for processing */ fun getBase64StringFromBlobUrl (blobUrl: String): String { Log.i ("JavascriptInterface/getBase64StringFromBlobUrl", "Downloading $blobUrl ...")
A work around would be to convert Blob URL to a Blob Object, then to a Base64 data on the web side. The native side will download the attachment in Base64 data according to the mime type specified in the prefix of the Base64 data.
Ok I had the same problem working with webviews, I realized that WebViewClient can't load "blob URLs" as Chrome Desktop client does. I solved it using Javascript Interfaces. You can do this by following the steps below and it works fine with minSdkVersion: 17. First, transform the Blob URL data in Base64 string using JS. Second, send this string to a Java Class and finally convert it in an available format, in this case I converted it in a ".pdf" file.
Before continue you can download the source code here :). The app is developed in Kotlin and Java. If you find any error, please let me know and I will fix it:
https://github.com/JaegerCodes/AmazingAndroidWebview
First things first. You have to setup your webview. In my case I'm loading the webpages in a fragment:
public class WebviewFragment extends Fragment {
WebView browser;
...
// invoke this method after set your WebViewClient and ChromeClient
private void browserSettings() {
browser.getSettings().setJavaScriptEnabled(true);
browser.setDownloadListener(new DownloadListener() {
@Override
public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimeType, long contentLength) {
browser.loadUrl(JavaScriptInterface.getBase64StringFromBlobUrl(url));
}
});
browser.getSettings().setAppCachePath(getActivity().getApplicationContext().getCacheDir().getAbsolutePath());
browser.getSettings().setCacheMode(WebSettings.LOAD_DEFAULT);
browser.getSettings().setDatabaseEnabled(true);
browser.getSettings().setDomStorageEnabled(true);
browser.getSettings().setUseWideViewPort(true);
browser.getSettings().setLoadWithOverviewMode(true);
browser.addJavascriptInterface(new JavaScriptInterface(getContext()), "Android");
browser.getSettings().setPluginState(PluginState.ON);
}
}
Finally, create a JavaScriptInterface class. This class contains the script that is going to be executed in our webpage.
public class JavaScriptInterface {
private Context context;
public JavaScriptInterface(Context context) {
this.context = context;
}
@JavascriptInterface
public void getBase64FromBlobData(String base64Data) throws IOException {
convertBase64StringToPdfAndStoreIt(base64Data);
}
public static String getBase64StringFromBlobUrl(String blobUrl) {
if(blobUrl.startsWith("blob")){
return "javascript: var xhr = new XMLHttpRequest();" +
"xhr.open('GET', '"+ blobUrl +"', true);" +
"xhr.setRequestHeader('Content-type','application/pdf');" +
"xhr.responseType = 'blob';" +
"xhr.onload = function(e) {" +
" if (this.status == 200) {" +
" var blobPdf = this.response;" +
" var reader = new FileReader();" +
" reader.readAsDataURL(blobPdf);" +
" reader.onloadend = function() {" +
" base64data = reader.result;" +
" Android.getBase64FromBlobData(base64data);" +
" }" +
" }" +
"};" +
"xhr.send();";
}
return "javascript: console.log('It is not a Blob URL');";
}
private void convertBase64StringToPdfAndStoreIt(String base64PDf) throws IOException {
final int notificationId = 1;
String currentDateTime = DateFormat.getDateTimeInstance().format(new Date());
final File dwldsPath = new File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_DOWNLOADS) + "/YourFileName_" + currentDateTime + "_.pdf");
byte[] pdfAsBytes = Base64.decode(base64PDf.replaceFirst("^data:application/pdf;base64,", ""), 0);
FileOutputStream os;
os = new FileOutputStream(dwldsPath, false);
os.write(pdfAsBytes);
os.flush();
if (dwldsPath.exists()) {
Intent intent = new Intent();
intent.setAction(android.content.Intent.ACTION_VIEW);
Uri apkURI = FileProvider.getUriForFile(context,context.getApplicationContext().getPackageName() + ".provider", dwldsPath);
intent.setDataAndType(apkURI, MimeTypeMap.getSingleton().getMimeTypeFromExtension("pdf"));
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
PendingIntent pendingIntent = PendingIntent.getActivity(context,1, intent, PendingIntent.FLAG_CANCEL_CURRENT);
String CHANNEL_ID = "MYCHANNEL";
final NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
NotificationChannel notificationChannel= new NotificationChannel(CHANNEL_ID,"name", NotificationManager.IMPORTANCE_LOW);
Notification notification = new Notification.Builder(context,CHANNEL_ID)
.setContentText("You have got something new!")
.setContentTitle("File downloaded")
.setContentIntent(pendingIntent)
.setChannelId(CHANNEL_ID)
.setSmallIcon(android.R.drawable.sym_action_chat)
.build();
if (notificationManager != null) {
notificationManager.createNotificationChannel(notificationChannel);
notificationManager.notify(notificationId, notification);
}
} else {
NotificationCompat.Builder b = new NotificationCompat.Builder(context, CHANNEL_ID)
.setDefaults(NotificationCompat.DEFAULT_ALL)
.setWhen(System.currentTimeMillis())
.setSmallIcon(android.R.drawable.sym_action_chat)
//.setContentIntent(pendingIntent)
.setContentTitle("MY TITLE")
.setContentText("MY TEXT CONTENT");
if (notificationManager != null) {
notificationManager.notify(notificationId, b.build());
Handler h = new Handler();
long delayInMilliseconds = 1000;
h.postDelayed(new Runnable() {
public void run() {
notificationManager.cancel(notificationId);
}
}, delayInMilliseconds);
}
}
}
Toast.makeText(context, "PDF FILE DOWNLOADED!", Toast.LENGTH_SHORT).show();
}
}
EXTRA: If you want to share these downloaded files with other Apps create an xml file in: ..\res\xml\provider_paths.xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="."/>
</paths>
Finally add this provider to your AndroidManifest.xml file
<application ...>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths"/>
</provider>
<!-- some code below ->
Another approach is by using "Chrome Custom Tabs"
Java:
CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
CustomTabsIntent customTabsIntent = builder.build();
customTabsIntent.launchUrl(context, Uri.parse("https://stackoverflow.com"));
Kotlin:
val url = "https://stackoverflow.com/"
val builder = CustomTabsIntent.Builder()
val customTabsIntent = builder.build()
customTabsIntent.launchUrl(this, Uri.parse(url))
Sources:
https://stackoverflow.com/a/41339946/4001198
https://stackoverflow.com/a/11901662/4001198
https://stackoverflow.com/a/19959041/4001198
https://developer.android.com/training/secure-file-sharing/setup-sharing
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With