Flutter WebView與JS互動簡易指南

吉原拉麵發表於2019-04-02

本文采用Flutter官方WebView外掛:pub.dartlang.org/packages/we…

  WebView與JS互相呼叫是一個剛需,但是貌似現在大家寫的文章講的都不是很清楚,我這個簡易指南簡單粗暴地分為兩部分:JS呼叫FlutterFlutter呼叫JS拒絕花裡胡哨,保證一看就懂,一學就會。

  開始之前先簡單瞭解一下官方WebView所包含的API:

  • onWebViewCreated:在WebView建立完成後呼叫,只會被呼叫一次;
  • initialUrl:初始load的url;
  • javascriptMode:JS執行模式(是否允許JS執行);
  • javascriptChannels:JS和Flutter通訊的Channel;
  • navigationDelegate:路由委託(可以通過在此處攔截url實現JS呼叫Flutter部分);
  • gestureRecognizers:手勢監聽;
  • onPageFinished:WebView載入完畢時的回撥。

JS呼叫Flutter

  JS呼叫Flutter有兩種方法:使用javascriptChannels傳送訊息使用路由委託(navigationDelegate)攔截url

方法1:使用javascriptChannels傳送訊息

  javascriptChannels引數可以傳入一組Channels,我們可以定義一個_alertJavascriptChannel變數,這個channel用來控制JS呼叫Flutter的toast功能:

JavascriptChannel _alertJavascriptChannel(BuildContext context) {
    return JavascriptChannel(
        name: 'Toast',
        onMessageReceived: (JavascriptMessage message) {
          showToast(message.message);
        });
  }

WebView(
    javascriptChannels: <JavascriptChannel>[
        _alertJavascriptChannel(context),
    ].toSet(),
;
複製程式碼

  在上面的程式碼中,我們定義了一個_alertJavascriptChannel變數,並給它起了個name叫Toast,這個name屬性接收的是一個字串,它代表了JS呼叫Flutter時,雙方共同商定好了的一個協議,JS通過這個name去post對應的資訊給Flutter(API為name.postMessage('xxxxxx'))。我們在網頁部分寫一個簡單的button,點選後開始JS呼叫Flutter的邏輯:

<button onclick="callFlutter()">callFlutter</button>

function callFlutter(){
   Toast.postMessage("JS呼叫了Flutter");
}
複製程式碼

  onMessageReceived為Flutter接收到了JS的訊息之後的回撥,我們可以通過message.message來獲取JS發給我們的訊息內容。JavascriptMessage類暫時只有一個String型別的message成員變數,所以如果需要傳遞複雜資料,可以通過傳遞json字串來解決。

  程式碼重點:JavascriptChannel中的name要與JS中的name.postMessage()相對應!!

方法2:使用路由委託navigationDelegate攔截url

  navigationDelegate回撥在每次網頁路由地址發生變化的時候都會觸發,因此我們可以攔截特定的url來實現JS呼叫Flutter。
  同樣的,我們在網頁部分寫一個簡單的button,點選後跳轉路由"js://webview?arg1=111&args2=222"。我們可以和客戶端協商好一個scheme,比如這個例子裡面就是js://webview,我們可以在query string上帶上我們想要傳遞的引數:

<button onclick="callFlutter()">callFlutter</button>

function callFlutter(){
  /*約定的url協議為:js://webview?arg1=111&arg2=222*/
  document.location = "js://webview?arg1=111&args2=222";
}
複製程式碼

  在Flutter端,我們就可以在navigationDelegate回撥中攔截這個符合js://webviewscheme的路由地址了:

navigationDelegate: (NavigationRequest request) {
            if (request.url.startsWith('js://webview')) {
              showToast('JS呼叫了Flutter By navigationDelegate');
              print('blocking navigation to $request}');
              return NavigationDecision.prevent;
            }
            print('allowing navigation to $request');
            return NavigationDecision.navigate;
          },
複製程式碼

  我們通過return不同的值,告訴WebView怎麼處理這個路由:

  • NavigationDecision.prevent:阻止路由替換;
  • NavigationDecision.navigate:允許路由替換。

Flutter呼叫JS

  在WebView建立完成之後,我們可以拿到一個WebViewController,通過它的evaluateJavascript()方法,我們可以執行JS語句:

onWebViewCreated: (WebViewController webViewController) {
    _controller = webViewController;
},
······
floatingActionButton: FloatingActionButton(
        onPressed: () {
          _controller
              ?.evaluateJavascript('callJS("visible")')
              ?.then((result) {
                  // You can handle JS result here.
              });
        },
        child: Text('call JS'),
      ),
複製程式碼
  <p id="p1" style="visibility:hidden;">
    Flutter 呼叫了 JS.
    Flutter 呼叫了 JS.
    Flutter 呼叫了 JS.
  </p>
  
function callJS(message){
  document.getElementById("p1").style.visibility = message;
}
複製程式碼

  在上面的例子中,我們點選floatingActionButton後,就會去執行JS中的callJS()方法了,具體UI體現為:將隱藏的段落重新顯示。evaluateJavascript()返回值是一個Future,因此我們可以接收JS給我們的返回值,返回值格式請閱讀官方API註釋,要注意Android端和iOS端的返回值格式是不一樣的,Android端返回的是json字串,iOS暫時只支援string和string格式的NSArray,其他型別資料還不支援。
  這裡要注意的是,evaluateJavascript()方法,Flutter建議我們在onPageFinished回撥之後去執行,以保證所有的HTML都已經載入完畢了。因此在實際開發中,我這裡展示的這種直接將onWebViewCreated中的controller賦值的方法是不可取的,應該是使用FutureBuilder之類的方式去實現比較優雅(我在Gist上有完整的例子,大家可以看下)。

原始碼

  注意:原始碼中的initialUrl測試地址請自己生成!

相關文章