Flutter WebView与Vue.js网页通信postMessage空值报错问题
异常原因
这个报错是多个逻辑问题叠加导致的:
message.js中直接使用了未定义的userAgent变量,没有从window.navigator.userAgent取值,导致端环境判断逻辑完全失效。- 环境判断失效后代码走到else分支,尝试调用
window.opener.postMessage,但WebView中加载的页面不存在通过window.open打开它的父窗口,window.opener值为null,对null调用postMessage方法就会抛出你看到的TypeError。 - 桥接调用逻辑不匹配:你使用的
webview_flutter插件注册的JavascriptChannel,在Android和iOS端都是统一挂载在页面对应的window对象下,直接通过注册时设置的name属性调用即可,你写的iOS端webkit消息处理逻辑是原生WKWebView的通信方式,不适用于这个Flutter插件场景。 - 冗余错误代码:
main.js中对普通工具对象调用Vue.use()是无效写法,Vue插件必须暴露install方法才能被Vue.use()注册,你写的message对象原本不满足插件要求。
修复方案
分别调整Web端和Flutter端代码即可:
Web端修改
- 重写
message.js,修正环境判断逻辑,优先判断桥接对象是否存在(比UA判断更可靠),同时补全Vue插件需要的install方法:
const sendMessage = (message) => { // 正确获取UA做兼容判断 const userAgent = window.navigator.userAgent.toLowerCase(); // 优先判断Flutter注入的桥接对象是否存在 if (window.WebViewBridge) { // 注意如果传对象需要先序列化,webview_flutter默认只支持字符串传递 return window.WebViewBridge.postMessage(typeof message === 'string' ? message : JSON.stringify(message)); } // 兼容原生iOS WKWebView场景,不需要可以删除 if ((userAgent.indexOf('iphone') !== -1 || userAgent.indexOf('ipad') !== -1) && window.webkit?.messageHandlers?.webViewMessageHandler) { return window.webkit.messageHandlers.webViewMessageHandler.postMessage(message); } // 本地浏览器调试场景兜底 console.log('非App WebView环境,消息内容:', message); } export default { // 补全install方法支持Vue.use注册 install(Vue) { Vue.prototype.$sendMessage = sendMessage; }, sendMessage }
- 简化
main.js,删掉多余的全局挂载代码:
import Vue from 'vue' import App from './App.vue' import message from './message'; Vue.config.productionTip = false Vue.use(message); new Vue({ render: h => h(App) }).$mount('#app')
- 修改
App.vue的调用逻辑,直接使用Vue原型上挂载的方法,不需要重复引入message文件:
<template> <div class="main"> <div class="btn" @click="toast()">communicate!</div> </div> </template> <script> export default { name: 'App', methods:{ toast(){ console.log("message to Flutter App!") this.$sendMessage("hello") } }, }; </script> <style> .main{ height: 100vh; display: flex; align-items: center; justify-content: center; background-color: antiquewhite; } .btn{ background-color: black; color: #fff; padding: 10px 20px; border-radius: 10px; cursor: pointer; } </style>
Flutter端修改
补全缺失的组件定义,在消息接收回调中加提示逻辑,注意Android端的网络配置:
import 'package:flutter/material.dart'; import 'package:webview_flutter/webview_flutter.dart'; void main() => runApp( MaterialApp( title: 'Navigator', initialRoute: '/my', routes: { "/my": (context) => MyApp(), }, ), ); class MyApp extends StatefulWidget { const MyApp({Key? key}) : super(key: key); @override State<MyApp> createState() => _MyAppState(); } class _MyAppState extends State<MyApp> { @override Widget build(BuildContext context) { return Scaffold( // 不需要AppBar可以直接设为null appBar: const EmptyAppBar(), body: WebView( initialUrl: "http://你电脑的局域网IP:8080/", javascriptMode: JavascriptMode.unrestricted, javascriptChannels: <JavascriptChannel>{ _webToApp(context), } ) ); } } JavascriptChannel _webToApp(BuildContext context) { return JavascriptChannel( name: 'WebViewBridge', onMessageReceived: (JavascriptMessage result) { print('收到JS端消息: ${result.message}'); // 用系统自带组件弹提示,不需要引入第三方库 ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("收到网页消息:${result.message}")) ); }); } // 补全你代码里缺失的EmptyAppBar定义 class EmptyAppBar extends AppBar { const EmptyAppBar({Key? key}) : super( key: key, elevation: 0, backgroundColor: Colors.transparent ); }
调试注意事项
- 确保测试手机和运行前端服务的电脑在同一局域网下,Flutter代码里填写的IP是电脑的局域网IP,不要填
localhost或者127.0.0.1,这两个地址指向手机自身。 - Android端需要在
AndroidManifest.xml中添加网络权限:<uses-permission android:name="android.permission.INTERNET" />,同时在<application>标签添加android:usesCleartextTraffic="true",允许加载http明文地址。 webview_flutter3.0及以上版本不需要手动修改UA,桥接对象会在页面加载时自动注入。
内容的提问来源于stack exchange,提问作者Yesterday BetterThan
相关产品推荐
相关产品推荐

