基于webview_flutter实现JsBridge的简单封装

人气很高的flutter_webview_plugin,在打开多个WebView时会出错,而且缺少2个重要的功能:

  • 不能在JS中调用Flutter方法
  • 不能在H5进入某个URL之前拦截

虽然该插件不够完整,但是使用起来很方便,封装了很多功能,如果交互不多可以用该插件。

本文基于flutter官方webview_flutter实现JsBridge的简单封装,实现在H5页面点击按钮调起flutter弹窗,具体步骤如下:

环境搭建

1. Depend on it

Add this to your package’s pubspec.yaml file:

dependencies:
webview_flutter: ^0.3.22+1
2. Install it

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.

3. Import it

Now in your Dart code, you can use:

import 'package:webview_flutter/webview_flutter.dart';
4. For iOS,you need to add ios-Runner-Info.plist with
<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>一个成员类型为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<String, dynamic>类型,需要这里将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信息

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值