[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 出来ない
});