[Android] 004. WebView(AndroidView)

Jetpack ComposeでWebViewを使いたい場合はaccompanistのWebViewなどを使った方が楽かもしれませんがandroid.webkit.WebViewで実装してみました

accompanistのWebViewは以下を参照
https://google.github.io/accompanist/web/

WebView

とりあえず最低限と思われる実装
android.webkit.WebViewの使い方についての詳細は省きます

onProgressChangeイベントはUIスレッドに返すようにしてます

// AndroidManifestに以下の権限を追加してください
// <uses-permission android:name="android.permission.INTERNET" />
@Composable
fun WebView(
    url:String,
    modifier: Modifier = Modifier,
    onProgressChange: (progress:Int, url: String) -> Unit = { i: Int, s: String -> },
    onReceivedError: (error: WebResourceError?) -> Unit = {},
): WebView? {
    var progress by remember { mutableStateOf(100) }

    val webViewChromeClient = object: WebChromeClient() {
        override fun onProgressChanged(view: WebView?, newProgress: Int) {
            super.onProgressChanged(view, newProgress)
            // 同じイベントを発生させない
            if (newProgress > progress) {
                progress = newProgress
                CoroutineScope(Dispatchers.Main).launch {
                    onProgressChange(progress, view?.url.toString())
                }
            }
        }
    }

    val webViewClient = object: WebViewClient() {
        override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
            super.onPageStarted(view, url, favicon)
            progress = 0
            CoroutineScope(Dispatchers.Main).launch {
                onProgressChange(progress, url ?: "")
            }
        }

        override fun onPageFinished(view: WebView?, url: String?) {
            super.onPageFinished(view, url)
            // 同じイベントを発生させない
            if (progress < 100) {
                progress = 100
                CoroutineScope(Dispatchers.Main).launch {
                    onProgressChange(progress, url ?: "")
                }
            }
        }

        override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
            if (request?.url == null) return false

            val showOverrideUrl = request.url.toString()
            try {
                if (!showOverrideUrl.startsWith("http://")
                    && !showOverrideUrl.startsWith("https://")) {

                    Intent(Intent.ACTION_VIEW, Uri.parse(showOverrideUrl)).apply {
                        addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                        view?.context?.applicationContext?.startActivity(this)
                    }
                    return true
                }
            } catch (e:Exception) {
                return true
            }
            return super.shouldOverrideUrlLoading(view, request)
        }

        override fun onReceivedError(view: WebView?, request: WebResourceRequest?, error: WebResourceError?) {
            super.onReceivedError(view, request, error)
            onReceivedError(error)
        }
    }

    var webView by remember { mutableStateOf<WebView?>(null) }
    AndroidView(
        modifier = modifier,
        factory = { context ->
            WebView(context).apply {
                this.webViewClient = webViewClient
                this.webChromeClient = webViewChromeClient

                webView = this
                loadUrl(url)
            }
        }
    )

    return webView
}
<manifest>
    <uses-permission android:name="android.permission.INTERNET" />
</manifest>

使い方

url入力用のTextFieldの実装もありますが最低限の実装はWebVew([URL])だけで表示されます

var webView: WebView? = null
var text by remember { mutableStateOf("https://google.com") }
val keyboard = LocalSoftwareKeyboardController.current

Column(Modifier.fillMaxSize()) {
    TextField(
        value = text,
        onValueChange = { text = it },
        modifier = Modifier.fillMaxWidth(),
        singleLine = true,
        keyboardOptions = KeyboardOptions(imeAction = ImeAction.Go),
        keyboardActions = KeyboardActions(onGo = {
            keyboard?.hide()
            webView?.apply { loadUrl(text) } }
        ),
        leadingIcon = {
            IconButton(onClick = {
                webView?.apply { if (canGoBack()) goBack() }
            }) {
                Icon(Icons.Default.ArrowBack, null)
            }
        },
        trailingIcon = {
            IconButton(onClick = {
                webView?.apply { reload() }
            }) {
                Icon(Icons.Default.Refresh, null)
            }
        }
    )
    webView = WebView(
        text,
        Modifier.fillMaxSize(),
        onProgressChange = { progress, url ->
            if (progress == 100 && url.isNotEmpty()) {
                text = url
            }
        },
    )
}

Android Studio Giraffe 2022.3.1 built on June 29, 2023