Download Blob File from Website Inside Android Webviewclient

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



Leave a reply



Submit