When users tap a download link in your WebView, nothing happens by default. The WebView just sits there. That's where setDownloadListener
comes in.
The Problem
Let's say you have a simple WebView displaying a webpage with PDF downloads. Without a download listener, tapping those links does nothing. Users get frustrated because they expect the file to download, just like in a regular browser.
val webView = findViewById<WebView>(R.id.webView)
webView.loadUrl("https://example.com/page-with-downloads")
// Downloads won't work yet
How setDownloadListener Works
The setDownloadListener
method lets you intercept download requests before they happen. When a user taps a download link, Android calls your listener instead of trying to handle the download itself.
webView.setDownloadListener { url, userAgent, contentDisposition, mimeType, contentLength ->
// Handle the download here
downloadFile(url, userAgent, contentDisposition, mimeType, contentLength)
}
The listener gives you several parameters:
-
url
- The direct download URL -
userAgent
- Browser identification string -
contentDisposition
- Filename and attachment info from the server -
mimeType
- File type (like "application/pdf") -
contentLength
- File size in bytes
Under the Hood
When you set a download listener, the WebView's internal download handling changes. Here's what happens:
- User taps a download link
- WebView checks if it can handle the content type
- If it can't (or if it's marked as a download), it calls your listener
- Your listener receives the download parameters
- You decide what to do next
The WebView doesn't actually download anything at this point. It just hands you the information and says "your turn."
Simple Implementation
Here's a basic implementation that uses Android's DownloadManager:
private fun setupDownloadListener() {
webView.setDownloadListener { url, userAgent, contentDisposition, mimeType, contentLength ->
val request = DownloadManager.Request(Uri.parse(url))
// Extract filename from contentDisposition or URL
val filename = getFileName(contentDisposition, url)
request.apply {
setTitle(filename)
setDescription("Downloading file...")
setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, filename)
setMimeType(mimeType)
}
val downloadManager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
downloadManager.enqueue(request)
Toast.makeText(this, "Download started", Toast.LENGTH_SHORT).show()
}
}
private fun getFileName(contentDisposition: String?, url: String): String {
return if (contentDisposition != null && contentDisposition.contains("filename=")) {
contentDisposition.substringAfter("filename=").replace("\"", "")
} else {
url.substringAfterLast("/")
}
}
Permission Requirements
Don't forget about permissions. For downloads to work, you need storage permissions:
// In your manifest
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
// For Android 10+ (API 29+), you might also need:
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
And request them at runtime:
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
PERMISSION_REQUEST_CODE)
}
Common Issues
Downloads not triggering: Make sure your WebView has JavaScript enabled and the correct user agent set. Some servers check these before serving downloads.
webView.settings.apply {
javaScriptEnabled = true
userAgentString = "Mozilla/5.0 (Linux; Android 10) AppleWebKit/537.36"
}
Filename extraction failing: The contentDisposition parameter isn't always reliable. Sometimes it's null, sometimes it's malformed. Always have a fallback that extracts the filename from the URL.
Large file handling: The DownloadManager is great for most cases, but for very large files or when you need more control, consider using OkHttp or similar libraries.
Alternative Approaches
If you need more control over the download process, you can skip DownloadManager and handle it yourself:
webView.setDownloadListener { url, _, _, _, _ ->
// Use a custom download implementation
startCustomDownload(url)
}
private fun startCustomDownload(url: String) {
// Implement with OkHttp, Retrofit, or similar
// This gives you progress callbacks, retry logic, etc.
}
Testing Downloads
Testing download functionality can be tricky. I usually create a simple HTML page with different types of download links:
<!DOCTYPE html>
<html>
<body>
<a href="sample.pdf" download>PDF Download</a>
<a href="image.jpg" download>Image Download</a>
<a href="document.docx">Document Download</a>
</body>
</html>
Load this in your WebView and test each link type to make sure your download listener handles them correctly.
Final Thoughts
The setDownloadListener
is essential for any app that uses WebViews and needs to handle downloads. It's not complicated once you understand that the WebView is just passing the download responsibility to you.
The key is handling the different parameter combinations gracefully and providing good user feedback. Users should know when a download starts, when it completes, and where to find the file.
Most importantly, test with different file types and servers. Not all websites handle downloads the same way, and you'll want to make sure your implementation works across different scenarios.
Want to connect? You can follow me on X here.
Top comments (0)