StradaのWebViewとネイティブコード連携を理解する

2023/10/22に公開

みんな大好き(のはず)Turbo関連のネイティブコンポーネント連携ライブラリのStradaがついにリリースされたので実装を調べてみる。

https://strada.hotwired.dev/

前提

  • Turbo NativeでWebアプリが動いている。これはリモートのURLでよい。
  • Stimulusでコントローラーを記述している

テスト環境

https://turbo-native-demo.glitch.me/https://github.com/hotwired/turbo-ios のDemo Appで読み込むとStradaの機能を試せるようになってる。

git clone https://github.com/hotwired/turbo-ios
xed Demo/

サーバーサイドのコードは https://github.com/hotwired/turbo-native-demo にあり、これの実行にRailsはいらない(Express+EJSで書いてある)。

とりあえず https://turbo-native-demo.glitch.me/strada-menu のサンプルで説明。

このページをiOSアプリで開き、ボタンをタップするとWebViewのコンテキストの外でネイティブコードのUIAlertActionのAPIが呼び出される。

使い方

ViewとなるHTML

https://github.com/hotwired/turbo-native-demo/blob/b119bf6f66d0fc87e7f45654e08857ab66c9edcd/views/strada-menu.ejs#L17-L68

data-action="click@window->menu#hideOnClickOutside"と宣言されている部分はネイティブではなくWebのみにフォールバックされるViewになる。

data-action="click->bridge--menu#show click->menu#show"になっているelementがネイティブブリッジ用。
Stimulus.register("bridge--menu", BridgeMenuController) でアタッチされている

https://github.com/hotwired/turbo-native-demo/blob/main/public/javascript/controllers/bridge/menu_controller.js#L1-L25

this.send("display",...のブロックが肝でこれを呼び出すことでネイティブコンポーネントへメッセージを送信している。

コールバックにネイティブ→Webの逆方向の呼び出しが発生した時の関数がある。このサンプルの場合、クリックしたメニュー項目をViewに反映させるためのコード。

以下がネイティブコンポーネントの実装。send->onReceive()がペアになっている。この場合message.event='display'になっている。

https://github.com/hotwired/turbo-ios/blob/main/Demo/Strada/MenuComponent.swift#L7-L54

ネイティブからWebViewに返す時はreply()

WebView内で動作するControllerも同時に実行されていて was selectedと表示しているのはWebになる。

https://github.com/hotwired/turbo-native-demo/blob/main/public/javascript/controllers/menu_controller.js#L20-L23

WebViewの外部のViewをネイティブに実装することもできる。以下はAnddroidのViewを記述している個所。

https://github.com/hotwired/turbo-android/blob/bf945e18aed3d5749c9efc6e228d98bc012878af/demo/src/main/kotlin/dev/hotwire/turbo/demo/strada/MenuComponent.kt#L40-L60

https://github.com/hotwired/turbo-android/blob/bf945e18aed3d5749c9efc6e228d98bc012878af/demo/src/main/res/layout/menu_component_bottom_sheet.xml

開発フロー

Strada自体の使い方は以上で、ポイントとしては今までのHotwireシリーズ同様に

  1. Turobのみを使ってWebアプリを構成する
  2. Stimulusを追加してクライアントサイドを書く
  3. Turbo Nativeでハイブリッドアプリにする
  4. Stradaでブリッジコードを書いて拡張する

を段階的にデリバリーできるようになっている。

以降はStradaの内部実装を深掘りしたい。

strada-ios実装

https://github.com/hotwired/strada-ios

  1. WKWebViewをWKUserContentControllerで拡張してstrada.jsのインターフェイスをネイティブ側に付ける
  2. ブラウザ側(Webアプリ)にはwindow.Stradaが注入されていてこれを使った双方向のメッセージングプロトコルが実装されている

なのでWebアプリからこういうコードを直接実行するとメッセージングを直接使うことが可能。

Strada.web.send({
  id: "1",
  component: "menu",
  event: "display",
  data: {
    metadata: { url: window.location.href },
    title: "こんにちは",
    items: [{ index: 0, title: "さようなら" }],
  },
  callback: console.log('Strada callback'),
});

strada-android実装

https://github.com/hotwired/strada-android

  1. webview.addJavascriptInterface()でStradaNative(strada.js)を window.nativeBridgeにットする
  2. webview.evaluateJavascript()で生成したJavaScriptのコードを実行することでメッセージングをする

https://github.com/hotwired/strada-android/blob/d62653703b0b9527ef5ce162ed7ff414b73ffb8e/strada/src/main/kotlin/dev/hotwire/strada/Bridge.kt#L10-L29

https://github.com/hotwired/strada-android/blob/d62653703b0b9527ef5ce162ed7ff414b73ffb8e/strada/src/main/kotlin/dev/hotwire/strada/Bridge.kt#L100-L103

Discussion