坑!都替你踩完了 —— FlutterWeb开发避坑指南
在前一阵用flutter开发web的过程中,遇到了一些坑,遇到了一些与app端开发不一样的地方,当然也学到了一些新知识点,所以总结一下,一方面帮助他人,另一方面,自己也能加深记忆,要不容易忘。 (年纪大了?记忆力还不行了呢🙃🙃🙃)
一、启动运行乱码
没错,启动一个demo,遇到坑了,如图所示
点击Android Studio上方运行按钮,程序启动之后汉字文字显示乱码,这是由于flutter web
有三种渲染模式,auto
、html
和 canvaskit
,点击运行按钮(flutter build web命令
)默认的渲染模式为auto
,这种模式在移动端使用html
渲染,在pc端使用canvaskit
渲染。
解决办法 1: 用命令行运行,并指定渲染模式,就能解决问题。
// 指定渲染模式为html
flutter build web --web-renderer html
解决办法 2: 上面虽然能解决问题,但我习惯用按钮运行程序怎么办?当然也找到了其他办法。在程序包下web/index.html
文件中body
标签下copy如下代码。
<!--指定web运行模式-->
<!-- window.flutterWebRenderer = "canvaskit";-->
<script type="text/javascript">
window.flutterWebRenderer = "html";
</script>
<script src="main.dart.js" type="application/javascript"></script>
二、Debug启动运行断点失败
web开发和APP端开发一样,也可以断点。项目之初断点是可以的,但是不知道怎么的,debug可以运行,但断不到,很奇怪,花了一上午,发现同事因为发版改了下web/index.html
中head->base
标签下 href="***"
的值。
解决办法 :
// 之前,断点可用
<base href="$FLUTTER_BASE_HREF">
// 同事改动,断点不可用
<base href="git/******">
// 修复后,断点可用
<base href="/">
不能断点开发实在是麻烦。
三、Hot Reload热重载、点击浏览器刷新,都会重启整个程序
在APP端开发时,在某个页面点hot reload按钮,只会重新运行当前页面,但是在web中,点热重载会重启,这只是开发中的不方便。已经上线的程序,用户只要点击浏览器刷新就会重启整个程序,无论在哪个页面,都会回到第一个页面,这与我浏览网页的习惯明显是不符的。
查找原因,发现是flutter底层问题,仔细观察web页面是通过不同的url来确定的,而Flutter从始至终都是一个url,只是flutter在一个网页中绘制了不同的页面(与APP端原理一致),所以想解决问题就是要每个页面都有自己的url。
解决办法 : 用静态路由的方式跳转页面和传参,具体代码如下。
// 跳转与传参
static Future toName(String pageName, Map<String, dynamic> params) {
var uri = Uri(scheme: RoutePath.scheme, host: pageName, queryParameters: params);
return Navigator.of(currentContext).pushNamed(uri.toString());
}
// 取参方式
static Route<dynamic> generateRoute(RouteSettings settings) {
return PageRouteBuilder(
settings: settings,
pageBuilder: (BuildContext c, Animation<double> a,Animation<double> sa) {
var uri = Uri.parse(settings.name ?? ''); //解析页面名
switch (uri.host) {
case RoutePath.name:
return NamePage(uri.queryParameters); 、、传参
default:
return Scaffold(
body: Center(
child: Text('没有找到对应的页面:${settings.name}'),
),
);
}
});
}
通过以上方式,跳转时每个页面都会有自己的url和拼接的参数,这样刷新的时候就不会重启整个程序,会停留在当前页面。
四、用静态路由的方式跳转,全局变量,单例对象丢失,页面栈记录丢失。
没错,坑是连着的,我也是服了。当在某页面热重载或点击浏览器刷新,会停留在当前页面,但是无法返回,就算点击跳转至其他页面,也会报错,因为全局变量都已经丢失,比如:登录信息,用户信息,已经初始化的工具类对象等。
已经有人提了Issues,国内也有大神分析了原因和不完全结局方案
目前
flutter web
对于浏览器还是没有适配完全,无论Navigator1.0
还是Navigator2.0
,都存在不可解决的严重问题。目前来看google的对flutter web的意图,还是开发移动web并在App中通过webkit这种内核使用,并没有想开发者使用flutter web来开发真正的web应用,或者后续会完善这部分。
我的解决方案
- 所有页面依据用静态路由的方式跳转
- 本地维护一个路由历史记录备用栈,当用户刷新后,无法正常回退时启用,实现正常的回退效果(备用栈解决方案)
- 全局变量持久化,用
html.window.localStorage
或html.window.sessionStorage
并配合工厂模式持久化数据,当被触发刷新,会从本地重新赋值,比如:登录信息等。 - 弱化全局成员变量,非必要不使用全局类的变量,数据尽量放云端,页面间不耦合。
五、监测浏览器功能的方法
1. 浏览器刷新或关闭监听
可以使用函数onBeforeUnload
来检查选项卡是否正在关闭。它也可能检测到页面刷新。
import 'dart:html' as html;
html.window.onBeforeUnload.listen((event) async{
// do something
});
或者
import 'dart:html' as html;
html.window.onUnload.listen((event) async{
// do something
});
2. 浏览器的返回键监听
import 'dart:html' as html;
html.window.onPopState.listen((event){
// do something
});
3. 浏览器页面内的点击监听
import 'dart:html' as html;
html.window.onClick.listen((event){
// do something
});
4. 浏览器大小调整监听
import 'dart:html' as html;
html.window.onResize.listen((event){
// do something
});
5. 浏览器获取焦点监听
import 'dart:html' as html;
html.window.onFocus.listen((event){
// do something
});
6. 浏览器获取焦点时,键盘按下、抬起监听
import 'dart:html' as html;
html.window.onKeyDown(onKeyUp).listen((event){
// do something
});
六、引用 import 'dart:html' 运行提示报错
多端运行,如果引用了html会提示报错。
解决办法 : 可以引用第三方universal_html 2.0.8,帮封装了一层,支持多端。
universal_html :适用于所有平台的“dart:html”,包括 Flutter 和服务器端。简化跨平台开发和 HTML / XML 处理。
七、可点击提示
在平常浏览网页时,鼠标滑动到可点击的文字或按钮上,鼠标“箭头”会变成一个“小手”,或背景出现颜色变化提示。 Flutter中常用的GestureDetector()手势工具,虽然可以实现点击等回调,但是鼠标滑动到可点击区域,鼠标“箭头”并不会变成“小手”,在交互上不符合大众使用网页的习惯。
解决办法 : 使用InkWell
替换GestureDetector
,用InkWell
包住的按钮或文字,鼠标悬停,就会出现小手。
Ink(
width: width,
height: height,
color: color,
child: InkWell(
focusColor: Colors.transparent,
highlightColor: Colors.transparent,
splashColor: Colors.transparent,
hoverColor: const Color(0x0818a7fb),
onTap: onTap,
child: Center(child: this)))
分析源码可知,内部用MouseRegion
监听了鼠标位置,那什么是MouseRegion
呢?
八、鼠标监听控件
MouseRegion 相对于APP端,web端多了个鼠标,可以实现app实现不了的交互效果,比如悬停,划过,进入退出某区域等,都可以用MouseRegion实现。
MouseRegion的属性和说明
字段 | 属性 | Col3 |
---|---|---|
onEnter | PointerEnterEventListener | 鼠标进入区域时的回调 |
onExit | PointerHoverEventListener | 鼠标退出区域时的回调 |
onHover | PointerExitEventListener | 鼠标在区域内移动时的回调 |
cursor | MouseCursor | 鼠标悬停区域时的光标样式 |
opaque | bool | 是否阻止检测鼠标 |
child | Widget | 子组件 |
九、本地存储
1. localStorage
web也会涉及到本地存储,本地持久化那肯定想到的就是shared_preferences: 2.0.13
,目前该插件已经支持所有平台,满足日常开发。
打开实现web
端的源码,里面使用html.window.localStorage
进行存储,以这种方式即使在浏览器关闭并重新打开时仍然存在。
存储没有过期日期的数据,并且只能通过 JavaScript 或清除浏览器缓存/本地存储的数据来清除,但有些场景的数据仅需要关闭选项卡或浏览器后就不需要了,用localStorage
就不太合适,还需要手动删除。
2. sessionStorage
那谁合适呢? sessionStorage
html.window.sessionStorage
会为已打开的页面维护一个单独的存储区域(只要浏览器打开,包括页面重新加载和恢复)
仅为已打开的页面存储数据,这意味着数据将一直存储到浏览器(或选项卡)关闭为止。
3. 小结
多端使用兼容web,那shared_preferences
再合适不过了,比如存token、版本记录、日志信息等。
但Flutter运行在浏览器上时,浏览器刷新后,全局的静态变量会初始化,所以刷新前就需要本地化储存,用localStorage
存储太重,也会影响下次程序运行数据的准确性,使用sessionStorage
就比localStorage
更合适,关闭浏览器或选项卡就会自动清理。
最后
这是目前遇到有价值的坑,后面遇到新的也会持续更新。
Flutter开发web上,不是特别复杂的业务逻辑,展示没啥问题;路由和全局变量上的坑还是挺严重的,希望官方后续给出更好的解决方案。
点赞、收藏mark一下✨✨万一用到时找不到了呢🌚
转载自:https://juejin.cn/post/7111984589086588959