写点什么

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

评论

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

导师带赚钱一对一QQ——玩家经验分享

泛目录代码编程

导师一对一带赚钱的玩法——玩家经验分享

泛目录代码编程

导师一对一带赚钱——玩家经验分享

泛目录代码编程

颠覆传统数据集,开启人工智能新篇章

百度开发者中心

人工智能 数据 文心一言

聚焦2023全球智博会 百度CIO李莹:大模型加速工作生产力变革

Geek_2d6073

人工智能在新能源和环境领域的角色

测吧(北京)科技有限公司

测试

导师带赚钱一对一大发——玩家经验分享

泛目录代码编程

彩票的导师都是怎么赚钱的——玩家经验分享

泛目录代码编程

导师一对一带赚钱联系方式——玩家经验分享

泛目录代码编程

如何使用Python构建一个抄袭检测系统?

高端章鱼哥

Python nlp NLP 大模型

大模型时代的规则与挑战

百度开发者中心

人工智能 生成式AI 文心一言

引领生产力新时代

百度开发者中心

人工智能 文心一言

四维纵横与用友达成战略合作,携手打造企业数据智能新基座

YMatrix 超融合数据库

用友 战略合作 超融合数据库 YMatrix 四维纵横

导师一对一带你盈利可靠吗——玩家经验分享

泛目录代码编程

创新引擎,重新定义生产力

百度开发者中心

人工智能 教育 生成式AI 文心一言

DevSecOps 中的漏洞管理(上)

禅道项目管理

DevOps

物联网的崭新时代:AI驱动的智能世界

测吧(北京)科技有限公司

测试

金牌导师团队计划——玩家经验分享

泛目录代码编程

Photoshop 2024 (ps智能ai绘图) for Mac v25.0beta中文激活版

mac

图像处理软件 苹果mac Windows软件 Photoshop 2024 ps2024

网上那种加导师赚钱是真的吗——玩家经验分享

泛目录代码编程

大数据平台数据安全具体措施有哪些?有推荐的吗?

行云管家

大数据平台 大数据平台安全

3天上手Ascend C编程丨带你认识Ascend C基本概念及常用接口

华为云开发者联盟

人工智能 华为云 昇腾 华为云开发者联盟 企业号9月PK榜

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