[Android] Javascript Interface

# Javascript Interfaceとは？
スマホアプリ内にウェブページを埋め込むwebViewを使う際、
フロントエンドのコードがスマホアプリのネイティブコードを叩く手段が提供されている。

それがJavascriptInterface


# 使い方導入
## kotlin実装
```kotlin
class MyJavascriptInterfaces {
    @JavascriptInterface
    fun hello(){
        Log.i("webViewApp", "Hello, World")
    }
}
```
というJavascript Interfaceを搭載したクラスを作り
```kotlin
binding.webView.apply {
    settings.javaScriptEnabled = true
    addJavascriptInterface(MyJavascriptInterfaces(), "MyJavascriptInterface")
    loadUrl("https://www.google.co.jp/")
}
```
webViewのaddJavascriptInterface関数を使用して登録するだけ。簡単。

## フロントエンドから叩く方法
```javascript
MyJavascriptInterface.hello()
```
で叩ける。

もしもリリース済みのアプリで
1. JavascriptInterfaceを導入しようとしている場合
2. JavascriptInterfaceの関数を追加しようとしている場合

は、過去のアプリ上でエラーが発生しないよう
```javascript
MyJavascriptInterface?.hello?.()
```
とすると安全である。

# 実際に動かしてみる
webViewアプリを起動して
Chrome上で `chrome://inspect/#devices` を開いてください

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3838545/223de778-7c8d-7526-3a01-40c2834cda39.png)

このように現在起動しているwebView画面の一覧が出て来ます。
スマホでGoogle Chrome等のブラウザを開いているとそれも一覧に出て来ます。

inspectを押すと見慣れた物が出て来ます。
先ほどのJavascriptInterface呼び出し関数をconsole上で実行すると

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3838545/3a749110-d225-f6e5-88f7-2ead15e633ee.png)
エラーが発生せず
LogCatを確認すると `Hello, World` が正常に出力されています。

# 取り扱える値
誰かドキュメント探して教えてください(土下座)！！！探しても見つからなかったので実際に動かして試しました。

結論から言うと、引数と戻り値ともに
- Boolean
- Int
- Double
- String
- String?
が取り扱える。

引数限定として
- StringArray
も渡せる。

```kotlin
@JavascriptInterface
fun getInt(v: Int): Int {
    return v+2
}

@JavascriptInterface
fun getBoolean(v: Boolean): Boolean {
    return !v
}

@JavascriptInterface
fun getDouble(v: Double): Double {
    return v+1
}

@JavascriptInterface
fun getString(v: String): String {
    return "hello~ $v"
}

@JavascriptInterface
fun getOptionalString(v: String?): String? {
    return v?.let{"hello~ $it"}
}

@JavascriptInterface
fun getStringArray(v: Array<String>) {
    v.forEach {
        element -> Log.i("WebViewApp", element)
    }
}
```

## 応用例
Stringが扱えるので、jsonを一回stringに変換すれば相互にjsonの受け渡しが可能です。
```kotlin
@JavascriptInterface
fun getJsonString(v: String): String {
    val json: JSONObject = JSONObject(v)
    val name: String = json.get("name") as String
    val age: Int = json.get("age") as Int
    json.put("status", "alive")
    return json.toString()
}
```


```javascript
let character = {"name": "taro", "age":25}
// jsonをstringに変換すると共にJavascriptInterfaceで値の受け渡し
let returnString = MyJavascriptInterface.getJsonString(JSON.stringify(character))
// stringをjsonに変換
let json = JSON.parse(returnString)
```

# 注意点
このJavascriptInterfaceは `UIスレッド上` で実行されます。その為できるだけ重たい処理をさせる事は避けた方が無難です。

もしも重たい処理をしたい場合は明確に別スレッドやコルーチンを使用して非同期で処理をするのがオススメです。

```kotlin
@JavascriptInterface
fun heavyTask1(): String {
    Thread.sleep(10000)
    return "DONE!"
}

@JavascriptInterface
fun heavyTask2(callback: String) {
    CoroutineScope(Dispatchers.IO).launch {
        Thread.sleep(10000)
        val returnValue = "DONE!"
        withContext(Dispatchers.Main){
            binding.webView.evaluateJavascript("javascript:$callback('$returnValue')", null)
        }
    }
}
```

```javascript
function handleResult(result) {
    console.log(result);
}

HeavyJavascriptInterfaces.heavyTask2("handleResult")
```
heavyTask1では10秒の間、UI処理が中断されてしまいユーザー体験が悪くなります。
一方でheavyTask2では処理自体は別スレッドで実行し、evaluateJavascriptでcallBack関数を呼んでいます。

# サンプルレポジトリ
https://github.com/holndal/javascriptInterfaceScratch