Shion のもくログ(旧: Shion の技術メモ)

使った技術のメモや、うまくいかなかった事とかを綴ります

PR

[Android] WebViewBuilder の話

概要

Android のWebView 関連設定をビルダー形式でいっぺんにできるようにしたものです。

https://github.com/tshion/AndroidPreparation/tree/released/webviewbuilder

問題点

まずWebView 関連設定は用途に応じて下記のクラスを使い分ける必要があります。

クラス名 設定可能項目例
WebChromeClient JavaScript のalert 呼び出しのハンドリング
WebSettings JavaScript の有効化
WebViewClient
(WebViewClientCompat)
URL 変更時のハンドリング

例えば下記の挙動が出来るWebView を作るとします。

  • JavaScript のalert() が呼ばれた時に、ネイティブ側のダイアログを表示する
  • Web ページ内のJavaScript が実行できる
  • Web ページ内のリンクでtel:// から始まる場合は、Android の電話アプリを呼び出す
<!doctype html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <button onclick="callAlert()">alert</button>
    <a href="tel://????????">tel</a>

    <script>
        function callAlert() {
            alert('Called by JavaScript!')
        }
    </script>
</body>
</html>

上記のHTML をassets に配置したとする時、普通に実装すると下記のようにコードになるかと思われます。

class BeforeActivity : AppCompatActivity(R.layout.web) {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val webView = findViewById<WebView>(R.id.webview_target)
        webView.settings.javaScriptEnabled = true
        webView.webChromeClient = MyWebChromeClient()
        webView.webViewClient = MyWebViewClient()
        webView.loadUrl("file:///android_asset/index.html")
    }


    private class MyWebChromeClient : WebChromeClient() {

        override fun onJsAlert(
            view: WebView?,
            url: String?,
            message: String?,
            result: JsResult?
        ): Boolean {
            AlertDialog.Builder(view!!.context)
                .setMessage(message)
                .setOnDismissListener {
                    result?.cancel()
                }
                .setPositiveButton("OK", null)
                .show()
            return true
        }
    }

    private class MyWebViewClient : WebViewClient() {

        override fun shouldOverrideUrlLoading(
            view: WebView?,
            request: WebResourceRequest?
        ): Boolean {
            if (request?.url?.scheme?.equals("tel", true) == true) {
                Intent(Intent.ACTION_DIAL).apply {
                    data = request.url
                }.also { view?.context?.startActivity(it) }
                return true
            } else {
                return false
            }
        }
    }
}

導入することで変わること

下記のような形で設定できるので拡張クラスを意識することなく、スッキリ記述できます。

class AfterActivity : AppCompatActivity(R.layout.web) {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        WebViewBuilder()
            .javaScriptEnabled(true)
            .onJsAlert { view, _, message, result ->
                AlertDialog.Builder(view!!.context)
                    .setMessage(message)
                    .setOnDismissListener {
                        result?.cancel()
                    }
                    .setPositiveButton("OK", null)
                    .show()
                true
            }
            .shouldOverrideUrlLoading { view, request ->
                if (request?.url?.scheme?.equals("tel", true) == true) {
                    Intent(Intent.ACTION_DIAL).apply {
                        data = request.url
                    }.also { view?.context?.startActivity(it) }
                    true
                } else {
                    false
                }
            }
            .into(findViewById<WebView>(R.id.webview_target))
            .loadUrl("file:///android_asset/index.html")
    }
}

設計について

各設定用クラスに対応したインターフェースを定義し、デフォルト実装を使ってそのクラスの設定方法を実装しています。 ビルダー本体(WebViewBuilder) は、そのラッピング用インターフェースを継承し、全ての機能を集約しています。

元のクラス ラッピング用のインターフェース
WebChromeClient WebChromeClientContract
WebSettings WebSettingsContract
WebViewClient
(WebViewClientCompat)
WebViewClientContract

今後の課題、やりたいこと

jCenter からの移行

jCenter が閉鎖してしまうので、maven などへの移行を検討しています。

ユースケースの整理とテストコード整備

例えばWeb 版のGoogle Map を表示する際は最低でも下記の設定が必要になります。

  • JavaScript の有効化
  • 位置情報の有効化
  • 位置情報許可ダイアログのハンドリング

上記のようにユースケースを洗い出して、一つずつテストコードを整備することで挙動の確認をしていきたいです。

Xamarin.Android への展開

Java の無名クラスはその内部でoverride の記述が出来ますが、C# の文法では出来ないので、このビルダーが役に立つ可能性があります。 なので実装が固まったら、いつか展開してみたいです。

webView.SetWebChromeClient(new MyWebChromeClient
{
    // override 出来ない
});
PR