How to download blob url in webview
i used this code in my project and is worked for me, try it!
and don't forget to add this code to onCreateView function
1 -
webView.getSettings().setDefaultTextEncodingName("utf-8");
webView.addJavascriptInterface(new JavaScriptInterface(getContext()), "Android");
2 -
Code:
public class JavaScriptInterface {
private static String fileMimeType;
private final Context context;
public JavaScriptInterface(Context context) {
this.context = context;
}
@JavascriptInterface
public void getBase64FromBlobData(String base64Data) throws IOException {
convertBase64StringToFileAndStoreIt(base64Data);
}
public static String getBase64StringFromBlobUrl(String blobUrl,String mimeType) {
if(blobUrl.startsWith("blob")){
fileMimeType = mimeType;
return "javascript: var xhr = new XMLHttpRequest();" +
"xhr.open('GET', '"+ blobUrl +"', true);" +
"xhr.setRequestHeader('Content-type','" + mimeType +";charset=UTF-8');" +
"xhr.responseType = 'blob';" +
"xhr.onload = function(e) {" +
" if (this.status == 200) {" +
" var blobFile = this.response;" +
" var reader = new FileReader();" +
" reader.readAsDataURL(blobFile);" +
" reader.onloadend = function() {" +
" base64data = reader.result;" +
" Android.getBase64FromBlobData(base64data);" +
" }" +
" }" +
"};" +
"xhr.send();";
}
return "javascript: console.log('It is not a Blob URL');";
}
private void convertBase64StringToFileAndStoreIt(String base64PDf) throws IOException {
final int notificationId = 1;
String currentDateTime = DateFormat.getDateTimeInstance().format(new Date());
String newTime = currentDateTime.replaceFirst(", ","_").replaceAll(" ","_").replaceAll(":","-");
Log.d("fileMimeType ====> ",fileMimeType);
MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton();
String extension = mimeTypeMap.getExtensionFromMimeType(fileMimeType);
final File dwldsPath = new File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_DOWNLOADS) + "/" + newTime + "_." + extension);
String regex = "^data:" + fileMimeType + ";base64,";
byte[] pdfAsBytes = Base64.decode(base64PDf.replaceFirst(regex, ""), 0);
try {
FileOutputStream os = new FileOutputStream(dwldsPath);
os.write(pdfAsBytes);
os.flush();
os.close();
} catch (Exception e) {
Toast.makeText(context, "FAILED TO DOWNLOAD THE FILE!", Toast.LENGTH_SHORT).show();
e.printStackTrace();
}
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(extension));
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);
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.stat_sys_download_done)
.build();
if (notificationManager != null) {
notificationManager.createNotificationChannel(notificationChannel);
notificationManager.notify(notificationId, notification);
}
}
Toast.makeText(context, "FILE DOWNLOADED!", Toast.LENGTH_SHORT).show();
}
}
How to download pdf in android through react native using web view package?
I think you should change the logic without using webview. In native languages(Swift/Java), WebView has similar issues, This means we couldn't implement the feature with react-native-webview,
So please try with rn-fetch-blob.
I want to suggest you "rn-fetch-blob" to download the file, and store it on android file system using "fs".
Maybe you can get the local url in file system.
Then, load the file on webview with the above local url.
Flutter WebView blob pdf download
Downloading file with blob url is tricky and not supported out of the box in the current state of webviews in Flutter. There are 3 popular plugins
- flutter_webview_plugin - (community)
- Webview_flutter (official)
- flutter_inappwebview
There is a note at README in community repository
We are working closely with the Flutter Team to integrate all the
Community Plugin features in the Official WebView Plugin. We will try
our best to resolve PRs and Bugfixes, but our priority right now is to
merge our two code-bases. Once the merge is complete we will deprecate
the Community Plugin in favor of the Official one
There is a lot of work yet to build fully working and bugfree webview. Currently for more challenging tasks like this mentioned here, the best attempt is to use flutter_inappwebview which is very popular and used by a lot people with success. There is issue associated with blob files. As we can see in your snippet you already used this plugin. To download blob file you can try convert blob:url to base64 like in this case Download Blob file from Website inside Android WebViewClient
Possible workaround
To your webview (_controller
) add JavaScriptHandler
. I would assume onWebViewCreated
might be ok.
controller.addJavaScriptHandler(
handlerName: _webViewHandlerName,
callback: (data) async {
if (data.isNotEmpty) {
final String receivedFileInBase64 = data[0];
final String receivedMimeType = data[1];
// NOTE: create a method that will handle your extensions
final String yourExtension =
_mapMimeTypeToYourExtension(receivedMimeType); // 'pdf'
_createFileFromBase64(
receivedFileInBase64, 'YourFileName', yourExtension);
}
},
);
JavaScript handler will receive two values stored in array. First argument is file encoded in base64 and second one is mimeType e.g. application/pdf
. Having information about mimeType allows us to get information about extension which should be used to save file with.
They can be easly mapped application/pdf => 'pdf' etc.
_createFileFromBase64(String base64content, String fileName, String yourExtension) async {
var bytes = base64Decode(base64content.replaceAll('\n', ''));
final output = await getExternalStorageDirectory();
final file = File("${output.path}/$fileName.$yourExtension");
await file.writeAsBytes(bytes.buffer.asUint8List());
print("${output.path}/${fileName}.$yourExtension");
await OpenFile.open("${output.path}/$fileName.$yourExtension");
setState(() {});
}
Finally where blob url can be handled the JavaScript is invoked.
onDownloadStart: (controller, url) async {
print("onDownloadStart $url");
var jsContent = await rootBundle.loadString("assets/js/base64.js");
await controller.evaluateJavascript(
source: jsContent.replaceAll("blobUrlPlaceholder", url));
},
Javascript (I prefer to load it as an asset base64.js, better than hardcoded in dart code)
It opens blob url and pass to encodedBase64 data and mimeType as arguments to our handler blobToBase64Handler
in dart.
var xhr = new XMLHttpRequest();
var blobUrl = "blobUrlPlaceholder";
console.log(blobUrl);
xhr.open('GET', blobUrl, true);
xhr.responseType = 'blob';
xhr.onload = function(e) {
if (this.status == 200) {
var blob = this.response;
var reader = new FileReader();
reader.readAsDataURL(blob);
reader.onloadend = function() {
var base64data = reader.result;
var base64ContentArray = base64data.split(",") ;
var mimeType = base64ContentArray[0].match(/[^:\s*]\w+\/[\w-+\d.]+(?=[;| ])/)[0];
var decodedFile = base64ContentArray[1];
console.log(mimeType);
window.flutter_inappwebview.callHandler('blobToBase64Handler', decodedFile, mimeType);
};
};
};
xhr.send();
source: Download Blob file from Website inside Android WebViewClient
source: How to decode base64 PDF string in Flutter?
It's not clean and looks hacky but could not find better and easier
How to download a blob URI using AlamoFire
After a few days, I was able to figure out how to download a blob URL without WKDownloadDelegate. The following code builds upon this answer.
A message handler needs to be created to respond to JS messages. I created this in the makeUIView
function
webModel.webView.configuration.userContentController.add(context.coordinator, name: "jsListener")
Inside your WKNavigationDelegate, you need to add this code on a navigation action.
NOTE: Since I use SwiftUI, all of my variables/models are located in the parent class (UIViewRepresentable coordinator).
func webView(_ webView: WKWebView,
decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
if let url = navigationAction.request.url, let scheme = url.scheme?.lowercased() {
if scheme == "blob" {
// Defer to JS handling
parent.webModel.executeBlobDownloadJS(url: url)
decisionHandler(.cancel)
} else {
decisionHandler(.allow)
}
}
}
Here's the JS to request for the blob stored in the browser memory. I added this JS in a wrapper function which called evaluateJavaScript
with the url for cleanliness of my code.
function blobToDataURL(blob, callback) {
var reader = new FileReader()
reader.onload = function(e) {callback(e.target.result.split(",")[1])}
reader.readAsDataURL(blob)
}
async function run() {
const url = "\(url)"
const blob = await fetch(url).then(r => r.blob())
blobToDataURL(blob, datauri => {
const responseObj = {
url: url,
mimeType: blob.type,
size: blob.size,
dataString: datauri
}
window.webkit.messageHandlers.jsListener.postMessage(JSON.stringify(responseObj))
})
}
run()
In addition to the returned JS object, I had to make a struct where I can deserialize the JSON string:
struct BlobComponents: Codable {
let url: String
let mimeType: String
let size: Int64
let dataString: String
}
I then took the messages sent to the WKScriptMessageHandler and interpreted them for saving to files. I used the SwiftUI file mover here, but you can do anything you want with this content.
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
guard let jsonString = message.body as? String else {
return
}
parent.webModel.blobDownloadWith(jsonString: jsonString)
}
In my web model (needed to import CoreServices):
func blobDownloadWith(jsonString: String) {
guard let jsonData = jsonString.data(using: .utf8) else {
print("Cannot convert blob JSON into data!")
return
}
let decoder = JSONDecoder()
do {
let file = try decoder.decode(BlobComponents.self, from: jsonData)
guard let data = Data(base64Encoded: file.dataString),
let uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, file.mimeType as CFString, nil),
let ext = UTTypeCopyPreferredTagWithClass(uti.takeRetainedValue(), kUTTagClassFilenameExtension)
else {
print("Error! \(error)")
return
}
let fileName = file.url.components(separatedBy: "/").last ?? "unknown"
let path = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let url = path.appendingPathComponent("blobDownload-\(fileName).\(ext.takeRetainedValue())")
try data.write(to: url)
downloadFileUrl = url
showFileMover = true
} catch {
print("Error! \(error)")
return
}
}
Related Topics
Disable iPhone "Save Image" Popup
Attempt to Add a Rule to a CSS Stylesheet Gives "The Operation Is Insecure" in Firefox
Webkit Scrollbar Dynamic Styling
Changing Leaflet Markercluster Icon Color, Inheriting the Rest of the Default CSS Properties
How to Measure the Space That a Text Will Take in JavaScript
Jquery Select a Div with a Certain Class, That Doesn't Have Another Class
Getting a Reference to the Parent Iframe
Nav-Collapse Not Working [Twitter Bootstrap]
CSS or JavaScript to Highlight Certain Area of Image Opacity
Width of the Parent Container with Jquery
Catch JavaScript Event in iOS Wkwebview with Swift
Navigate to Screen After Opening a Notification
Internet Explorer Adds Height- and Width-Attributes to a Newly Appended Image Automatically