QCon 演讲火热征集中,快来分享技术实践与洞见! 了解详情
写点什么

如何在 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:414109

评论

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

无处不在的 Kubernetes,难用的问题解决了吗?

阿里巴巴云原生

阿里云 Kubernetes 容器 云原生 难题攻克

Android C++系列:JNI中的线程操作

轻口味

c++ android jni 11月日更

架构实战营 - 模块八作业

Alex.Wu

模块三作业

忘记喝水的猫

架构训练营

再见了,纸币!数字人民币,席卷而来

CECBC

PS 基础操作 - 缩放、抓手、移动工具

hepingfly

ps教程 ps学习笔记

Go语言,定时器的使用

微客鸟窝

Go 语言 11月日更

设计消息队列存储消息数据的 MySQL 表格

缘分呐

自动化前端页面性能测试介绍

刘冉

大前端 软件测试 性能测试 软件自动化测试

如何使用 Prometheus Kafka Adapter 向 Kafka 推送 Prometheus 数据

耳东@Erdong

kafka Prometheus Adapter 11月日更

14 K8S之对外访问容器服务

穿过生命散发芬芳

k8s 11月日更

PS 基础操作 - 文件格式和存储

hepingfly

ps教程 ps学习笔记

进击的Java(六)

ES_her0

11月日更

架构营模块三作业

GTiger

架构实战营

低碳企业和个人如何参与碳交易市场?

石云升

碳中和 11月日更 碳交易

模块三作业

周文

「架构实战营」

.NET6新东西--CallerArgumentExpression

喵叔

11月日更

基于Serverless的测试服务化-理论篇

刘冉

软件测试 自动化测试 测试服务化

AI辅助测试与自动化测试

刘冉

AI 自动化测试

极客时间架构实战营作业八

jjn0703

架构实战营

和12岁小同志搞创客开发:手撕代码,做一款声控灯

不脱发的程序猿

少儿编程 DIY 传感器 创客开发 Arduino

微服务中台技术之延迟中心实践

小江

Java redis kafka 延时队列

模块三作业

lxz

【Promise 源码学习】第七篇 - 实现 Promise 返回值 x 的处理

Brave

源码 Promise 11月日更

在线英文名随机生成器

入门小站

工具

《PyTorch深度学习实战》复习之环境搭建

IT蜗壳-Tango

11月日更

【LeetCode】键值映射Java题解

Albert

算法 LeetCode 11月日更

Golang Gin 框架入门介绍(一)

liuzhen007

11月日更

架构实战营 - 模块八作业

en

#架构实战营

模块三作业:学生管理系统架构设计文档

赵先生

架构实战营

模块三课后作业 - 详细架构设计文档 - 外包学生管理系统

渐行渐远

架构实战营

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