人气很高的flutter_webview_plugin,在打开多个WebView时会出错,而且缺少2个重要的功能:
虽然该插件不够完整,但是使用起来很方便,封装了很多功能,如果交互不多可以用该插件。
本文基于flutter官方webview_flutter实现JsBridge的简单封装,实现在H5页面点击按钮调起flutter弹窗,具体步骤如下:
Add this to your package’s pubspec.yaml file:
dependencies:
webview_flutter: ^0.3.22+1
You can install packages from the command line with Flutter:
$ flutter pub get
Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.
Now in your Dart code, you can use:
import 'package:webview_flutter/webview_flutter.dart';
<key>io.flutter.embedded_views_preview</key>
<string>YES</string>
否则报错:
[VERBOSE-2:shell.cc(207)] Dart Error: Unhandled exception:
PlatformException(unregistered_view_type, trying to create a view with an unregistered type, unregistered view type: ‘plugins.flutter.io/webview’)
#0 StandardMethodCodec.decodeEnvelope (package:flutter/src/services/message_codecs.dart:569:7)
#1 MethodChannel.invokeMethod (package:flutter/src/services/platform_channel.dart:321:33)
#2 PlatformViewsService.initUiKitView (package:flutter/src/services/platform_views.dart:168:41)
#3 _UiKitViewState._createNewUiKitView (package:flutter/src/widgets/platform_view.dart:621:71)
#4 _UiKitViewState._initializeOnce (package:flutter/src/widgets/platform_view.dart:571:5)
#5 _UiKitViewState.didChangeDependencies (package:flutter/src/widgets/platform_view.dart:581:5)
#6 StatefulElement._firstBuild (package:flutter/src/widgets/framework.dart:4380:12)
#7 ComponentElement.mount (package:flutter/src/widgets/framework.dart:4208:5)
#8 Element.inf<…>
main.dart
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import './JsBridgeUtil.dart';
const String kNavigationExamplePage = '''
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Navigation Delegate Example</title>
<style type="text/css">
.click-button {
width: 300px;
height: 100px;
font-size: 40px;
}
</style>
</head>
<body>
<p style="color: #161823; font-size: 40px">
The test of method execution from H5 to Flutter
</p>
<Button class="click-button" onclick="executeMethod()">点我</Button>
</body>
<script defer type="text/javascript">
function executeMethod() {
let paramObject = {
method: 'showToast',
params: {
message: 'User Agent: ' + navigator.userAgent
}
}
ViaBridge.postMessage(JSON.stringify(paramObject));
}
</script>
</html>
''';
void main() => runApp(MaterialApp(home: WebViewExample()));
class WebViewExample extends StatefulWidget {
@override
_WebViewExampleState createState() => _WebViewExampleState();
}
class _WebViewExampleState extends State<WebViewExample> {
// webview 加载进度设置
final Completer<WebViewController> _controller =
Completer<WebViewController>();
final String contentBase64 =
base64Encode(const Utf8Encoder().convert(kNavigationExamplePage));
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flutter WebView example'),
),
body: Builder(builder: (BuildContext context) {
return WebView(
// 要显示的url
initialUrl: 'data:text/html;base64,$contentBase64',
// JS执行模式 默认是disabled
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (WebViewController webViewController) {
// 在WebView创建完成后会产生一个 webViewController
_controller.complete(webViewController);
},
// 使用javascriptChannel JS可以调用Flutter
// javascriptChannels参数接受Set一个成员类型为JavascriptChannel的Set集合
javascriptChannels: <JavascriptChannel>[
_toasterJavascriptChannel(context),
].toSet(),
// 拦截请求
navigationDelegate: (NavigationRequest request) {
if (request.url.startsWith('https://www.youtube.com/')) {
print('blocking navigation to $request}');
return NavigationDecision.prevent;
}
print('allowing navigation to $request');
return NavigationDecision.navigate;
},
onPageStarted: (String url) {
print('Page started loading: $url');
},
onPageFinished: (String url) {
print('Page finished loading: $url');
},
gestureNavigationEnabled: true,
);
}),
);
}
JavascriptChannel _toasterJavascriptChannel(BuildContext context) {
return JavascriptChannel(
name: 'ViaBridge',
onMessageReceived: (JavascriptMessage message) async{
String jsonStr = message.message;
JsBridgeUtil.executeMethod(context, JsBridgeUtil.parseJson(jsonStr));
});
}
}
JsBridgeUtil.dart
import 'dart:convert';
import './JsBridge.dart';
import 'package:flutter/material.dart';
/*
* JsBridge执行类
**/
class JsBridgeUtil {
/// 将json字符串转化成对象
static JsBridge parseJson(String jsonStr) {
JsBridge jsBridgeModel = JsBridge.fromMap(jsonDecode(jsonStr));
return jsBridgeModel;
}
/// 向H5暴露接口调用
static executeMethod(BuildContext context, JsBridge jsBridge) async{
if (jsBridge.method == 'showToast') {
Scaffold.of(context).showSnackBar(
SnackBar(content: Text(jsBridge.params['message'])),
);
}
}
}
JsBridge.dart
/*
* 定义JsBridge的基本结构
**/
class JsBridge {
String method; // 方法名
Map params; // 传递数据
Function callback; // 执行回调
JsBridge(this.method, this.params, this.callback);
// jsonEncode方法中会调用实体类的这个方法。如果实体类中没有这个方法,会报错。
Map toJson() {
Map map = new Map();
map["method"] = this.method;
map["params"] = this.params;
map["callback"] = this.callback;
return map;
}
// jsonDecode(jsonStr)方法返回的是Map类型,需要这里将map转换成实体类
static JsBridge fromMap(Map<String, dynamic> map) {
JsBridge jsBridgeModel = new JsBridge(map['method'], map['params'], map['callback']);
return jsBridgeModel;
}
@override
String toString() {
return "JsBridge: {method: $method, params: $params, callback: $callback}";
}
}
在H5页面点击按钮“点我“就可以调起flutter弹窗,展示navigator.userAgent信息