写点什么

如何在 Flutter 和 JavaScript 之间创建通信桥?

  • 2020-12-31
  • 本文字数:4172 字

    阅读完需:约 14 分钟

如何在 Flutter 和 JavaScript 之间创建通信桥?

我在之前的一篇文章中解释了如何在 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 个方法:


  1. fromFlutter - 我们从 flutter 调用这个方法,用一个字符串参数表示页面新标题

  2. sendBack - 我们会调用这个方法来与 Flutter 通信。在这个方法中,我们会发送一个字符串消息。


稍后,我们将看看 sendBack 的内容,在那之前,我们需要先在我们的应用程序中设置好 WebView。


别忘了在 pubspec.yaml 中的 assets 部分增加 index.html(使用正确的缩进)


dependencies:  flutter:    sdk: flutter  webview_flutter: ^1.0.7  cupertino_icons: ^1.0.0

dev_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。


你可以从这里链接获取到本文描述的应用程序的源代码。


最后要注意的两点:


  1. webview_flutter包中的alert方法已经损坏

  2. 为了在 iOS 中使用这个包,你必须将如下主键添加到你的 info.plist 文件中:


<key>io.flutter.embedded_views_preview</key><string>yes</string>
复制代码


如果你想要了解更多关于 Flutter 和 WebViews 的信息,你可以看看这两个有用的资源:



原文链接


How To Create a Communication Bridge Between Flutter And JavaScript


2020-12-31 15:414103

评论

发布
暂无评论
发现更多内容

泰国Ascend Money用开源软件加快应用交付

BeeWorks

阅读

以两种异步模型应用案例,深度解析Future接口

华为云开发者联盟

Java 模型 异步 FutureTask Future接口

小白必看,通俗易懂的LockSupport

程序猿阿星

Java并发 线程协作 LockSupport 线程间通信

3D地图与3D柱状图联合使用

ThingJS数字孪生引擎

大前端 地图 可视化 数字孪生

Java程序员必备框架—Spring全家桶的前世今生详细梳理

北游学Java

Java spring

如何在二三线城市月薪过万(一)看完这篇后端简历优化,包你面试不断

小鲍侃java

面试 后端

坐下来谈谈如何写好一份简历?

童欧巴

面试 大前端 简历

云图说 | 华为云医疗智能体,智联大健康,AI药物研发

华为云开发者联盟

AI 药物研发 医疗智能体

TensorFlow Recommenders: Quickstart

毛显新

深度学习 tensorflow 推荐系统 keras

兰宝环保 | 新体系“行动计划”中化工制药行业VOCs废气治理要点

叶落便知秋

教你如何成为解决问题的高手

孙叫兽

高手 解决问题

中易通科技禾禾实验室产品培育田

叶落便知秋

详解Camtasia的PPT录制功能

淋雨

视频剪辑 Camtasia 录屏软件

Druid 如何开启查询日志

HoneyMoose

AI论文解读丨融合视觉、语义、关系多模态信息的文档版面分析架构VSR

华为云开发者联盟

语义 视觉 多模态信息 文档版式 VSR

数据对AI的重要性:采访首席数据官Rick McFarland - LexisNexis Legal & Professional

BeeWorks

阅读

CloudQuery 首次开放API,v1.4.1将开放「部门导入」和「用户导入」

BinTools图尔兹

数据库 OpenAPI 数据库管控

INFRA-JOY微服务治理验证工程实践分享

徐敏

微服务 自动化测试 全链路压测 性能压测

充满科技感的农业,是年轻人的『菜』吗?

百度大脑

人工智能

模块三

Winston

带你走进“华为链”

华为云开发者联盟

区块链 高性能 华为链 自研区块链平台 自主可控

Java集合源码总结分析

手把手体验远程开发,确实爽

程序员鱼皮

Java Python 软件 大前端 后端

在线常用crontab表达式大全验证解析

入门小站

工具

Python OpenCV 图像处理之直方图相关知识细节,学点细的

梦想橡皮擦

7月日更

区块链去中心化钱包开发|开发去中心化钱包

Geek_23f0c3

钱包系统开发 去中心化交易所系统开发 去中心化钱包 去中心化交易所

模块三作业

河马先生

架构实战营

程序员如何快速成长为IT精英

孙叫兽

程序员 成长 IT职场

第三届WICC北京落幕 展现开发者服务生态与建设新方向

融云 RongCloud

男人要慢,SQL要快:记一次慢SQL优化

艾小仙

剖根问底:Java 不能实现真正泛型的原因是什么?

沉默王二

java

如何在 Flutter 和 JavaScript 之间创建通信桥?_大前端_tomerpacific_InfoQ精选文章