DEV Community

boryanz
boryanz

Posted on

WebView's download listener on Android: How it works?

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
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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:

  1. User taps a download link
  2. WebView checks if it can handle the content type
  3. If it can't (or if it's marked as a download), it calls your listener
  4. Your listener receives the download parameters
  5. 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("/")
    }
}
Enter fullscreen mode Exit fullscreen mode

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" />
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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"
}
Enter fullscreen mode Exit fullscreen mode

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.
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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)