'How to upload multiple files (webview) in kotlin in 2021?

I am trying to enable multiple images upload on my WKWebView in Android (Kotlin). My web page has an input type file with the multiple attribute. I searched several similar questions which sometime are quite old and could not find one that resolves my problem.

I tried to add:

intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)

Then I also found an answer using an Array like this, which seems to make sense

filePathCallback: ValueCallback<Array<Uri>>?

However neither worked in the end. I am posting my cleaned-up code as it is now. I can select pictures one by one but not several at a time (note that this works well if I access the URL directly in a browser on Android or on my iOS app..).

package foo.bar.app

import android.Manifest
import android.annotation.SuppressLint
import android.app.AlertDialog
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.net.http.SslError
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.view.KeyEvent
import android.webkit.*
import android.widget.ProgressBar
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.onesignal.OneSignal

open class WebActivity : AppCompatActivity() {

    companion object {
        const val PAGE_URL = "pageUrl"
        const val MAX_PROGRESS = 100

        // File selector
        private var mUploadMessage: ValueCallback<Uri>? = null
        private var uploadMessage: ValueCallback<Array<Uri>>? = null
        const val FILECHOOSER_RESULTCODE = 1
        const val REQUEST_SELECT_FILE = 100

        fun newIntent(context: Context, pageUrl: String): Intent {
            val intent = Intent(context, WebActivity::class.java)
            intent.putExtra(PAGE_URL, pageUrl)
            return intent
        }
    }

    var pageUrl: String = ""

    private lateinit var binding: ActivityWebBinding    

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityWebBinding.inflate(layoutInflater)
        val view = binding.root
        setContentView(view)
        pageUrl = intent.getStringExtra(PAGE_URL) ?: ""

        initWebView()
        setWebClient()

        loadUrl(appLinkUrl, "")
    }
    
    private fun initWebView() {
        binding.webView.settings.loadWithOverviewMode = true
        binding.webView.settings.useWideViewPort = true
        binding.webView.settings.domStorageEnabled = true
        binding.webView.settings.databaseEnabled = true

        binding.webView.webViewClient = object : WebViewClient() {
            override
            fun onReceivedSslError(view: WebView?, handler: SslErrorHandler?, error: SslError?) {
                when (error!!.primaryError) {
                    SslError.SSL_UNTRUSTED -> handler?.cancel()
                    SslError.SSL_EXPIRED -> handler?.cancel()
                    SslError.SSL_IDMISMATCH -> handler?.cancel()
                    SslError.SSL_NOTYETVALID -> handler?.cancel()
                }
                //handler?.proceed()
            }

            override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
                val url: String = request?.url.toString()
                binding.webView.loadUrl(url)
                return true
            }

            // for backward compatibility
            override fun shouldOverrideUrlLoading(view: WebView?, url: String): Boolean {
                binding.webView.loadUrl(url)
                return true
            }

            override fun onPageFinished(view: WebView, url: String) {
                super.onPageFinished(view, url)
            }
        }
    }

    private fun setWebClient() {

        binding.webView.webChromeClient = object : WebChromeClient() {

            // File selector
            // For Android 4.1
            fun openFileChooser(uploadMsg: ValueCallback<Uri>, acceptType: String, capture: String) {
                mUploadMessage = uploadMsg
                val i = Intent(Intent.ACTION_GET_CONTENT)
                i.addCategory(Intent.CATEGORY_OPENABLE)
                i.type = "image/*"
                [email protected](Intent.createChooser(i, "File Chooser"), FILECHOOSER_RESULTCODE)
            }

            // File selector
            protected fun openFileChooser(uploadMsg: ValueCallback<Uri>) {
                mUploadMessage = uploadMsg
                val intent = Intent(Intent.ACTION_GET_CONTENT)
                intent.addCategory(Intent.CATEGORY_OPENABLE)
                intent.type = "*/*"
                startActivityForResult(Intent.createChooser(intent, "File Chooser"), FILECHOOSER_RESULTCODE)
            }

            // File selector
            override fun onShowFileChooser(webView: WebView?, filePathCallback: ValueCallback<Array<Uri>>?, fileChooserParams: FileChooserParams?): Boolean {
                uploadMessage?.onReceiveValue(null)
                uploadMessage = null

                uploadMessage = filePathCallback

                val intent = fileChooserParams!!.createIntent()
                intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
                intent.addCategory(Intent.CATEGORY_OPENABLE)
                intent.type = "image/*"
                try {
                    startActivityForResult(intent, REQUEST_SELECT_FILE)
                    return true
                } catch (e: ActivityNotFoundException) {
                    uploadMessage = null
                    Toast.makeText(applicationContext, "Cannot Open File Chooser", Toast.LENGTH_LONG).show()
                    return false
                }
                return true
            }
        }
    }

    // File selector
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            if (requestCode == REQUEST_SELECT_FILE) {
                if (uploadMessage == null)
                    return
                print("result code = $resultCode")
                val results: Array<Uri>? = WebChromeClient.FileChooserParams.parseResult(resultCode, data)
                uploadMessage?.onReceiveValue(results)
                uploadMessage = null
            }
        } else if (requestCode == FILECHOOSER_RESULTCODE) {
            if (null == mUploadMessage)
                return
            // Use MainActivity.RESULT_OK if you're implementing WebView inside Fragment
            // Use RESULT_OK only if you're implementing WebView inside an Activity
            val result = if (intent == null || resultCode != RESULT_OK) null else intent.data
            mUploadMessage?.onReceiveValue(result)
            mUploadMessage = null
        } else
            Toast.makeText(applicationContext, "Failed to Upload Image", Toast.LENGTH_LONG).show()
    }

    private fun loadUrl(pageUrl: String, postData: String) {
        if( postData == "" ) binding.webView.loadUrl(pageUrl)
        else binding.webView.postUrl(pageUrl, postData.toByteArray())
    }

    override fun onNewIntent(intent: Intent) {
        super.onNewIntent(intent)
        handleIntent(intent)
    }

    private fun handleIntent(intent: Intent) {
        val appLinkAction = intent.action
        val appLinkData: Uri? = intent.data
        if (Intent.ACTION_VIEW == appLinkAction) {
            if (appLinkData != null) {
                appLinkData.path?.let { loadUrl(it, "") }
            }
        }
    }        
}

I am trying to manage older Android versions, my objective is to support multiple file uploads in more recents versions at least. I can live without this feature on older versions.

Let me know if you see something I am missing.

Thanks for the help !

C



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source