我在之前的一篇文章中解釋了如何在 Android 和 iOS 中創建通信橋,作為後續,我認為解釋一下如何在 Flutter 中創建通信橋也是一個不錯的想法。雖然這可能看起來是一件很簡單的事情,但你很快就會意識到,要使這個功能正常工作需要一些工作。
首先,重要的是意識到(在撰寫本文時)Flutter 還沒有內置對嵌入式 WebView 的支持。這意味著,在 Kotlin 或 Swift 中的本地應用程式中你可以實例化一個 WebView 組件,而在 Flutter 中你不能直接將 WebView 組件添加到你的應用程式中。
在創建一個新的 Flutter 項目後,我們需要使用webview_flutter包來使得能夠使用 WebView。我們會向 pubspec.yaml 文件中添加依賴:
dependencies: flutter: sdk: flutter webview_flutter: ^1.0.7然後,我們需要運行 Pub get 或者在終端中:
flutter pub get然後,我們需要在 main.dart 文件中導入這個包:
import 'package:webview_flutter/webview_flutter.dart';如果你還沒有清理初始項目的代碼,現在可以著手清理了。在你刪除所有的注釋、浮動操作按鈕以及與之相關的所有內容之後,你就會剩下以下內容(為了展示,我添加了一個文本部件):
import 'dart:convert';import 'package:flutter/material.dart';import 'package:webview_flutter/webview_flutter.dart';void main() { runApp(MyApp());}class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Communication Bridge', theme: ThemeData( primarySwatch: Colors.blue, visualDensity: VisualDensity.adaptivePlatformDensity, ), home: MyHomePage(title: 'Native - JS Communication Bridge'), ); }}class MyHomePage extends StatefulWidget { MyHomePage({Key key, this.title}) : super(key: key); final String title; @override _MyHomePageState createState() => _MyHomePageState();}class _MyHomePageState extends State<MyHomePage> { WebViewController _controller; @override Widget build(BuildContext context) { return Text( "Flutter JS-Native Communication Bridge" 這段代碼的顯示結果如下:
添加本地文件
因為我們將使用一個嵌有 JavaScript 代碼的本地 html 文件,我們需要在項目中創建它。Flutter 應用程式中的所有本地資源都需要存放在一個 assets 目錄中。通過右鍵點擊左側面板,然後選擇新建->目錄,來在你的主項目層創建一個 assets 目錄。這個目錄需要是 android 目錄的一個同級目錄。
然後,繼續在 assets 目錄中創建 index.html 文件。
<html> <head> <title>My Local HTML File</title> </head> <body> <h1 id="title">Hello World!</h1> <script type="text/javascript"> function fromFlutter(newTitle) { document.getElementById("title").innerHTML = newTitle; sendBack(); } function sendBack() { messageHandler.postMessage("Hello from JS"); } </script> </body></htm你會注意到,我們在 html 文件的 JavaScript 部分寫了 2 個方法:
fromFlutter - 我們從flutter調用這個方法,用一個字符串參數表示頁面新標題sendBack - 我們會調用這個方法來與Flutter通信。在這個方法中,我們會發送一個字符串消息。稍後,我們將看看 sendBack 的內容,在那之前,我們需要先在我們的應用程式中設置好 WebView。
別忘了在pubspec.yaml中的assets部分增加index.html(使用正確的縮進)
dependencies: flutter: sdk: flutter webview_flutter: ^1.0.7 cupertino_icons: ^1.0.0dev_dependencies: flutter_test: sdk: flutter flutter: uses-material-design: true assets: - assets/index.html設置 WebView
由於我們已經將包導入了 main.dart 文件,因此需要將文本部件替換為一個 WebView 部件。
class _MyHomePageState extends State<MyHomePage> { WebViewController _controller; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Webview')), body: WebView( initialUrl: 'about:blank', onWebViewCreated: (WebViewController webviewController) { _controller = webviewController; _loadHtmlFromAssets(); }, ), ); } _loadHtmlFromAssets() async { String file = await rootBundle.loadString('assets/index.html'); _controller.loadUrl(Uri.dataFromString( file, mimeType: 'text/html', encoding: Encoding.getByName('utf-8')).toString()); }我們用一個 Scaffold 部件包裹 WebView(它的用途將在本文後面介紹),但是我們可以關註上面看到的 WebView 部件的不同欄位:
initialUrl - 用來定義WebView指向哪裡。這裡我們決定將它指向無,因為我們將加載本地html文件。onWebViewCreated - 一旦WebView被創建,我們將從包得到的一個回調。因為我們要保存從這個回調中獲取的控制器實例,所以我們創建了一個私有成員(_controller)來存儲它。你還會注意到,我們創建了一個名為_loadHtmlFromAssets 的方法,顧名思義,它會將我們的本地 html 文件加載到 WebView 中。
在這個方法中,我們使用我們的私有 WebViewController 實例,_controller,以及它的公開方法 loadUrl 來加載我們的本地 html 文件。由於這個方法中的邏輯,它的執行是異步的。
如果我們運行我們的應用程式,顯示如下:
通信(Flutter -> WebView)
現在,讓我們添加一些功能來調用我們在本地 html 文件中定義的 fromFlutter 方法。為此,我們將向我們的布局中增加一個浮動操作按鈕(Floating Action Button,FAB),其 onPressed 方法調用 fromFlutter 方法。這也是使用 Scaffold 部件的背後原因,這樣我們可以很容易地添加一個 FAB。
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Webview')), body: WebView( initialUrl: 'about:blank', javascriptMode: JavascriptMode.unrestricted, onWebViewCreated: (WebViewController webviewController) { _controller = webviewController; _loadHtmlFromAssets(); }, ), floatingActionButton: FloatingActionButton( child: const Icon(Icons.arrow_upward), onPressed: () { _controller.evaluateJavascript('fromFlutter("From Flutter")'); }, ), ); }為了從 Flutter 向我們加載的 html 發起調用,我們使用 evaluateJavascript 方法。為了能使用這個方法,我們必須向我們的 WebView 增加另外一個屬性,即 javascriptMode。上面,我們將這個屬性設為 unrestricted(無限制的)。如果我們不設置它,我們就不能在 Flutter 和 WebView 之間通信。
反向通信(WebView -> Flutter)
還記得前面說過要討論 senBack 方法的內容嗎?現在是時候來看看了。
function sendBack() { messageHandler.postMessage("Hello from JS");}在 sendBack 方法中,我們使用了一個稱為 messageHandler 的對象和一個名為 postMessage 的附加方法。如果你在原生應用程式中創建過通信橋,你就會意識到,一旦你設置了一個通信橋,你就會向 Javascript 層的全局 window 對象添加一個對象用於通信。你可以隨意給這個對象命名,只要你從 Javascript 向你的原生應用程式發起調用時使用那個名字就可以。
如何將這個對象添加到我們應用程式中的 Javascript 層呢?通過向我們的 WebView 部件添加一個 JavascriptChannels屬性:
class _MyHomePageState extends State<MyHomePage> { WebViewController _controller; final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>(); @override Widget build(BuildContext context) { return Scaffold( key: _scaffoldKey, appBar: AppBar(title: Text('Webview')), body: WebView( initialUrl: 'about:blank', javascriptMode: JavascriptMode.unrestricted, javascriptChannels: Set.from([ JavascriptChannel( name: 'messageHandler', onMessageReceived: (JavascriptMessage message) { _scaffoldKey.currentState.showSnackBar( SnackBar( content: Text(message) ) ); }) ]), onWebViewCreated: (WebViewController webviewController) { _controller = webviewController; _loadHtmlFromAssets(); }, ), floatingActionButton: FloatingActionButton( child: const Icon(Icons.arrow_upward), onPressed: () { _controller.evaluateJavascript('fromFlutter("From Flutter")'); }, ), ); 我們已經定義了一個 JavascriptChannel 和一個 onMessageReceived 處理器。我們給這個通道的命名是,messageHandler,我們用它來從我們加載的本地 html 文件向我們的原生層通信。
對於目光敏銳的人,你可以已經注意到新增了一個私有變量,_scaffoldKey。這是因為我們需要給我們的 Scaffold 部件增加一個主鍵,以便可以顯示 Snackbar。
你可以從這裡連結獲取到本文描述的應用程式的原始碼。
最後要注意的兩點:
webview_flutter包中的alert方法已經損壞為了在iOS中使用這個包,你必須將如下主鍵添加到你的info.plist文件中:<key>io.flutter.embedded_views_preview</key><string>yes</string>如果你想要了解更多關於 Flutter 和 WebViews 的信息,你可以看看這兩個有用的資源:
The Power Of WebViews In FlutterWebView_Flutter Package原文連結
How To Create a Communication Bridge Between Flutter And JavaScript
延伸閱讀:
Vue.js最佳靜態站點生成器對比-InfoQ
關注我並轉發此篇文章,即可獲得學習資料~若想了解更多,也可移步InfoQ官網,獲取InfoQ最新資訊~