My website use html2pdf(javascript) to generate pdf from html but have issues of download on android webview
: kotlin. When I want to click download in my webview
which display my website button it just crash. I was hoping from you guys who experience why it happened and how to overcome this? I'm still beginner of this android studio development.
In the website(javascript):
function generatePDF(){
var station_element = document.getElementById('station_content');
var inspect_element = document.getElementById('inspect_content');
window.scrollTo(0, 0);
// Delay execution for 500 milliseconds (adjust as needed)
setTimeout(function() {
if (station_element) {
var opt = {
margin: 50,
filename: 'myfile.pdf',
image: { type: 'jpeg', quality: 1 },
html2canvas: { scale: 5, width: 1300 },
jsPDF: { unit: 'pt', format: 'a4', orientation: 'portrait' }
};
html2pdf().set(opt).from(station_element).save();
} else if (inspect_element) {
var opt = {
margin: 60,
filename: 'myfile.pdf',
image: { type: 'jpeg', quality: 1 },
html2canvas: { scale: 2 },
jsPDF: { unit: 'pt', format: 'a4', orientation: 'portrait' }
};
html2pdf().set(opt).from(inspect_element).save();
}
}, 500); // 500 milliseconds delay
}
Android studio: MainActivity.kt
package com.coding.meet.webviewtoapp
class MainActivity : AppCompatActivity() {
private var webUrl = "https://smartappx.site"
private val multiplePermissionId = 14
private val multiplePermissionNameList =
if (Build.VERSION.SDK_INT >= 33) {
arrayListOf()
} else {
arrayListOf(
android.Manifest.permission.READ_EXTERNAL_STORAGE,
android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
)
}
private var isLoaded = false
private var doubleBackToExitPressedOnce = false
private val networkConnectivityObserver: NetworkConnectivityObserver by lazy {
NetworkConnectivityObserver(this)
}
private val loadingDialog: Dialog by lazy { Dialog(this) }
private val mainBinding: ActivityMainBinding by lazy {
DataBindingUtil.setContentView(this, R.layout.activity_main)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
loadingDialog.setContentView(R.layout.loading_layout)
loadingDialog.window!!.setLayout(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
loadingDialog.setCancelable(false)
loadingDialog.show()
val setting = mainBinding.webView.settings
setting.javaScriptEnabled = true
setting.allowFileAccess = true
setting.domStorageEnabled = true
setting.javaScriptCanOpenWindowsAutomatically = true
setting.supportMultipleWindows()
val snackbar =
Snackbar.make(mainBinding.root, "No Internet Connection", Snackbar.LENGTH_INDEFINITE)
.setAction("Wifi") { startActivity(Intent(Settings.ACTION_WIFI_SETTINGS)) }
networkConnectivityObserver.observe(this) {
when (it) {
Status.Available -> {
if (snackbar.isShown) {
snackbar.dismiss()
}
mainBinding.swipeRefresh.isEnabled = true
if (!isLoaded) loadWebView()
}
else -> {
showNoInternet()
snackbar.show()
mainBinding.swipeRefresh.isRefreshing = false
}
}
}
mainBinding.swipeRefresh.setOnRefreshListener {
if (!isLoaded) {
loadWebView()
} else {
setProgressDialogVisibility(false)
}
}
}
private fun setProgressDialogVisibility(visible: Boolean) {
if (visible) {
loadingDialog.show()
} else {
loadingDialog.dismiss()
mainBinding.swipeRefresh.isRefreshing = false
}
}
private fun showNoInternet() {
isLoaded = false
setProgressDialogVisibility(false)
gone(mainBinding.webView)
visible(mainBinding.noInternet.noInternetRL)
}
private fun loadWebView() {
gone(mainBinding.noInternet.noInternetRL)
visible(mainBinding.webView)
mainBinding.webView.loadUrl(webUrl)
mainBinding.webView.setDownloadListener {
url,
userAgent,
contentDisposition,
mimeType,
contentLength ->
Log.d("Url", url.trim())
Log.d("userAgent", userAgent)
Log.d("contentDisposition", contentDisposition)
Log.d("mimeType", mimeType)
Log.d("contentLength", contentLength.toString())
if (checkMultiplePermission()) {
download(url.trim(), userAgent, contentDisposition, mimeType, contentLength)
}
}
mainBinding.webView.webViewClient =
object : WebViewClient() {
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
setProgressDialogVisibility(true)
super.onPageStarted(view, url, favicon)
}
override fun shouldOverrideUrlLoading(
view: WebView?,
request: WebResourceRequest?,
): Boolean {
val url = request?.url.toString()
view?.loadUrl(url)
return super.shouldOverrideUrlLoading(view, request)
}
override fun onPageFinished(view: WebView?, url: String?) {
isLoaded = true
webUrl = url!!
setProgressDialogVisibility(false)
super.onPageFinished(view, url)
}
override fun onReceivedError(
view: WebView?,
request: WebResourceRequest?,
error: WebResourceError?,
) {
isLoaded = false
setProgressDialogVisibility(false)
super.onReceivedError(view, request, error)
}
}
}
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
if (event.action == KeyEvent.ACTION_DOWN) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
if (mainBinding.webView.canGoBack()) {
mainBinding.webView.goBack()
} else {
showToastExit()
}
return true
}
}
return super.onKeyDown(keyCode, event)
}
private fun showToastExit() {
when {
doubleBackToExitPressedOnce -> {
finish()
}
else -> {
doubleBackToExitPressedOnce = true
Toast.makeText(this, "Please Click Back Again to Exit", Toast.LENGTH_LONG).show()
Handler(Looper.getMainLooper())
.postDelayed({ doubleBackToExitPressedOnce = false }, 2000)
}
}
}
private fun download(
url: String,
userAgent: String,
contentDisposition: String,
mimeType: String,
contentLength: Long
) {
val folder = File(Environment.getExternalStorageDirectory().toString() + "/Download/Image")
if (!folder.exists()) {
folder.mkdirs()
}
Toast.makeText(this, "Download Started", Toast.LENGTH_SHORT).show()
val request = DownloadManager.Request(Uri.parse(url))
request.setMimeType(mimeType)
val cookie = CookieManager.getInstance().getCookie(url)
request.addRequestHeader("cookie", cookie)
request.addRequestHeader("User-Agent", userAgent)
request.setAllowedNetworkTypes(
DownloadManager.Request.NETWORK_WIFI or DownloadManager.Request.NETWORK_MOBILE
)
val fileName = URLUtil.guessFileName(url, contentDisposition, mimeType)
request.setTitle(fileName)
request.setNotificationVisibility(
DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED
)
request.setDestinationInExternalPublicDir(
Environment.DIRECTORY_DOWNLOADS,
"Image/$fileName"
)
val downloadManager = getSystemService(DOWNLOAD_SERVICE) as DownloadManager
downloadManager.enqueue(request)
}
private fun checkMultiplePermission(): Boolean {
val listPermissionNeeded = arrayListOf<String>()
for (permission in multiplePermissionNameList) {
if (
ContextCompat.checkSelfPermission(this, permission) !=
PackageManager.PERMISSION_GRANTED
) {
listPermissionNeeded.add(permission)
}
}
if (listPermissionNeeded.isNotEmpty()) {
ActivityCompat.requestPermissions(
this,
listPermissionNeeded.toTypedArray(),
multiplePermissionId
)
return false
}
return true
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray,
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == multiplePermissionId) {
if (grantResults.isNotEmpty()) {
var isGrant = true
for (element in grantResults) {
if (element == PackageManager.PERMISSION_DENIED) {
isGrant = false
}
}
if (isGrant) {
// here all permission granted successfully
Toast.makeText(this, "all permission granted successfully", Toast.LENGTH_LONG)
.show()
} else {
var someDenied = false
for (permission in permissions) {
if (
!ActivityCompat.shouldShowRequestPermissionRationale(this, permission)
) {
if (
ActivityCompat.checkSelfPermission(this, permission) ==
PackageManager.PERMISSION_DENIED
) {
someDenied = true
}
}
}
if (someDenied) {
// here app Setting open because all permission is not granted
// and permanent denied
appSettingOpen(this)
} else {
// here warning permission show
warningPermissionDialog(this) { _: DialogInterface, which: Int ->
when (which) {
DialogInterface.BUTTON_POSITIVE -> checkMultiplePermission()
}
}
}
}
}
}
}
}
This code is not mine I just test it with my demo website.
I tried to find solution on android platform but found none.
This the android logcat
Logcat IllegalArgumentException:
024-06-03 17:28:30.668 1264-2123 AlarmManager com.google.android.gms.persistent W alarm window unlikely to be respected [CONTEXT service_id=231 ] (Ask Gemini)
java.lang.IllegalArgumentException: alarm "NetworkLocationLocator" has short window length
at bngl.b(:com.google.android.gms@242013041@24.20.13 (190800-633713831):52)
at efni.f(:com.google.android.gms@242013041@24.20.13 (190800-633713831):119)
at efjs.r(:com.google.android.gms@242013041@24.20.13 (190800-633713831):107)
at efjs.f(:com.google.android.gms@242013041@24.20.13 (190800-633713831):27)
at efjp.apply(:com.google.android.gms@242013041@24.20.13 (190800-633713831):19)
at esdc.d(:com.google.android.gms@242013041@24.20.13 (190800-633713831):3)
at esdd.run(:com.google.android.gms@242013041@24.20.13 (190800-633713831):42)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at bomx.nL(:com.google.android.gms@242013041@24.20.13 (190800-633713831):1)
at bomx.dispatchMessage(:com.google.android.gms@242013041@24.20.13 (190800-633713831):138)
at android.os.Looper.loopOnce(Looper.java:201)
at android.os.Looper.loop(Looper.java:288)
at android.os.HandlerThread.run(HandlerThread.java:67)
--------- beginning of crash
2024-06-03 17:36:43.011 1639-1639 AndroidRuntime com.coding.meet.webviewtoapp E FATAL EXCEPTION: main (Ask Gemini)
Process: com.coding.meet.webviewtoapp, PID: 1639
java.lang.IllegalArgumentException: Can only download HTTP/HTTPS URIs: blob:https://smartappx.site/ac776f6d-9ba9-4d15-ab60-2249b485853b
at android.app.DownloadManager$Request.<init>(DownloadManager.java:468)
at com.coding.meet.webviewtoapp.MainActivity.download(MainActivity.kt:236)
at com.coding.meet.webviewtoapp.MainActivity.loadWebView$lambda$2(MainActivity.kt:148)
at com.coding.meet.webviewtoapp.MainActivity.$r8$lambda$17rajA2X3B1OvSyO9osdba9FNsQ(Unknown Source:0)
at com.coding.meet.webviewtoapp.MainActivity$$ExternalSyntheticLambda1.onDownloadStart(Unknown Source:7)
at g9.handleMessage(chromium-TrichromeWebViewGoogle6432.apk-stable-447211487:145)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loopOnce(Looper.java:201)
at android.os.Looper.loop(Looper.java:288)
at android.app.ActivityThread.main(ActivityThread.java:7839)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)
2024-06-03 17:39:19.433 1264-2123 AlarmManager com.google.android.gms.persistent W alarm window unlikely to be respected [CONTEXT service_id=231 ] (Ask Gemini)
java.lang.IllegalArgumentException: alarm "NetworkLocationLocator" has short window length
at bngl.b(:com.google.android.gms@242013041@24.20.13 (190800-633713831):52)
at efni.f(:com.google.android.gms@242013041@24.20.13 (190800-633713831):119)
at efjs.r(:com.google.android.gms@242013041@24.20.13 (190800-633713831):107)
at efjs.f(:com.google.android.gms@242013041@24.20.13 (190800-633713831):27)
at efjp.apply(:com.google.android.gms@242013041@24.20.13 (190800-633713831):19)
at esdc.d(:com.google.android.gms@242013041@24.20.13 (190800-633713831):3)
at esdd.run(:com.google.android.gms@242013041@24.20.13 (190800-633713831):42)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at bomx.nL(:com.google.android.gms@242013041@24.20.13 (190800-633713831):1)
at bomx.dispatchMessage(:com.google.android.gms@242013041@24.20.13 (190800-633713831):138)
at android.os.Looper.loopOnce(Looper.java:201)
at android.os.Looper.loop(Looper.java:288)
at android.os.HandlerThread.run(HandlerThread.java:67)
I had the similar issue the onDownloadStart
param url was appended with blob
. It was just in the url so I deleted it and everything works fine.
private fun download(
url: String,
userAgent: String,
contentDisposition: String,
mimeType: String,
contentLength: Long
) {
val folder = File(Environment.getExternalStorageDirectory().toString() + "/Download/Image")
if (!folder.exists()) {
folder.mkdirs()
}
Toast.makeText(this, "Download Started", Toast.LENGTH_SHORT).show()
// Remove the 'blob:' prefix if it exists
val cleanUrl = url.replace("blob:", "")
val request = DownloadManager.Request(Uri.parse(cleanUrl))
request.setMimeType(mimeType)
val cookie = CookieManager.getInstance().getCookie(cleanUrl)
request.addRequestHeader("cookie", cookie)
request.addRequestHeader("User-Agent", userAgent)
request.setAllowedNetworkTypes(
DownloadManager.Request.NETWORK_WIFI or DownloadManager.Request.NETWORK_MOBILE
)
val fileName = URLUtil.guessFileName(cleanUrl, contentDisposition, mimeType)
request.setTitle(fileName)
request.setNotificationVisibility(
DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED
)
request.setDestinationInExternalPublicDir(
Environment.DIRECTORY_DOWNLOADS,
"Image/$fileName"
)
val downloadManager = getSystemService(DOWNLOAD_SERVICE) as DownloadManager
downloadManager.enqueue(request)
}
Url that was coming from method -> blob:https://smartappx.site/ac776f6d-9ba9-4d15-ab60-2249b485853b
Url that you had to use -> https://smartappx.site/ac776f6d-9ba9-4d15-ab60-2249b485853b
Note : This is the easy way to do it
Another solution would be :
webView.setWebViewClient(new WebViewClient() {
@Override
public void onPageFinished(WebView view, String url) {
view.loadUrl("javascript:(function() {" +
"document.querySelector('a').addEventListener('click', function() {" +
" var xhr = new XMLHttpRequest();" +
" xhr.open('GET', this.href, true);" +
" xhr.responseType = 'blob';" +
" xhr.onload = function() {" +
" if (xhr.status === 200) {" +
" var reader = new FileReader();" +
" reader.onload = function() {" +
" var base64Data = reader.result.split(',')[1];" +
" Android.downloadBlob(base64Data, 'image/jpeg', 'myImage.jpg');" +
" };" +
" reader.readAsDataURL(xhr.response);" +
" }" +
" };" +
" xhr.send();" +
"});" +
"})();");
}
});
webView.addJavascriptInterface(new Object() {
@JavascriptInterface
public void downloadBlob(String base64Data, String mimeType, String fileName) {
byte[] data = Base64.decode(base64Data, Base64.DEFAULT);
saveFile(data, mimeType, fileName);
}
}, "Android");
private void saveFile(byte[] data, String mimeType, String fileName) {
File folder = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + "/Image");
if (!folder.exists()) {
folder.mkdirs();
}
File file = new File(folder, fileName);
try (FileOutputStream fos = new FileOutputStream(file)) {
fos.write(data);
Toast.makeText(this, "Download Complete", Toast.LENGTH_SHORT).show();
} catch (IOException e) {
e.printStackTrace();
}
// Notify the download manager of the new file
DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
downloadManager.addCompletedDownload(fileName, fileName, true, mimeType, file.getAbsolutePath(), data.length, true);
}
Use this in case my first solution doesn't work for you, since less code is always best more code is more prone to error and crashes.