flutter客户端项目适配web做的一些工作
# flutter客户端项目适配web做的一些工作
# 1 按平台引入一些不兼容的库/类,或者直接移除
- 比如dart:ffi, dart:io
条件引入示例:
import 'dart:io' if (dart.library.html) 'dart:html';
- 不能在代码里使用File(path), File类在io和html包里都有,但构造方法的参数个数不一致,直接导致编译失败
- 不能在代码里使用Platform类(调用到才报错,不会导致编译失败)
# 2 dio适配
if (getx.GetPlatform.isWeb) {
//var cookieJar = PersistCookieJar(storage: GetXStorageImpl());
//var cookieJar=PersistCookieJar(storage: Storage);
// DioUtils.instance.dio.interceptors.add(CookieManager(cookieJar));
//没用,httponly的cookie无法读取和保存
//web要设置options.extra["withCredentials"] = true; 但tmd还是没有用
/* interceptors0.add(InterceptorsWrapper(onRequest: (options, handler) async {
options.extra["withCredentials"] = true;
return handler.next(options);
}));*/
//要用专门给浏览器写的adapter,并开启withCredentials
BrowserHttpClientAdapter adapter = BrowserHttpClientAdapter();
adapter.withCredentials = true;
//还需要cookie本身host匹配 这个只能后台设置好,一般推荐设置泛二级域名,并且设置好cookie的跨域功: SameSite=None; Secure
DioUtils.instance.dio.httpClientAdapter = adapter;
} else {
getApplicationDocumentsDirectory().then((appDocDir) {
String appDocPath = appDocDir.path + "/cookies/";
var cookieJar = PersistCookieJar(storage: FileStorage(appDocPath));
//var cookieJar=PersistCookieJar(storage: Storage);
DioUtils.instance.dio.interceptors.add(CookieManager(cookieJar));
});
//pc代理自动读取和设置到dio
ProxyHepler.config(DioUtils.instance.dio)
.then((value) => null)
.onError((error, stackTrace) => null);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
请求拦截器在web上不允许修改ua:
if(!GetPlatform.isWeb){
//web不允许设置ua
options.headers['user-agent'] = packageUserAgent;
}
2
3
4
请求的header匹配以下不安全字符时,将被终止,具体参考如下:
Accept-Charset Accept-Encoding Connection Content-Length Cookie Cookie2 Content-Transfer-Encoding Date Expect Host Keep-Alive Referer TE Trailer Transfer-Encoding Upgrade User-Agent
# 3 本地调试的跨域问题
包括请求跨域和cookie跨域两个问题
除了localhost调试的跨域问题, 最终打包发布的跨域问题要在nginx或后台服务代码里配置.
# 半吊子的方式:
利用flutter-cors工具,修改pc上flutter sdk里chrome的配置,仅本机有效,仅请求跨域有效.
cookie跨域还得服务端修改或者用代理服务器修改. 不值得做.
具体见: https://blog.hss01248.tech/pages/cdc4b9/#%E8%A7%82%E5%AF%9F%E8%80%85
# 正确的解决方式
两种方式:
- 类似web端开发,引入self -proxy库,本地自建代理服务器进行代理请求.需要改项目代码,已包装好,一劳永逸解决请求跨域和cookie跨域,让你拥有和web开发一样的丝滑体验: https://pub.dev/packages/web_dev_proxy_shelf
# cookie问题
https://github.com/flutterchina/dio/issues/1131
https://github.com/flutterchina/dio/issues/1270
其实这些都不是dio框架的问题,也不是flutter编译的问题,而是后台配置的问题
只要后台配置了cookie跨域,flutter侧就可以正常使用cookie
必须后台配置,flutter编译成web后无法在拦截器里强制设置cookie,否则报错.这是浏览器的内部设定导致的.
见上面的列表
# 后台配置
整体请求配置跨域+ 返回cookie时配置: SameSite=None
ResponseCookie cookie2 = ResponseCookie.from("staff-token",token) // key & value
.httpOnly(false) // 禁止js读取
.secure(true) // 在http下也传输
// .domain("localhost")// 域名
.path("/") // path
.maxAge(24*3600*100L) // 有效期
.sameSite("None") // 大多数情况也是不发送第三方 Cookie,但是导航到目标网址的 Get 请求除外
.build();
// 设置Cookie Header
response.setHeader(HttpHeaders.SET_COOKIE, cookie2.toString());
2
3
4
5
6
7
8
9
10
整体配置跨域: 非必须.
也可以在nginx入口配置
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")//项目中的所有接口都支持跨域
.allowedOriginPatterns("*")//所有地址都可以访问,也可以配置具体地址
.allowCredentials(true)
.allowedMethods("*")//"GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS"
.maxAge(36000);// 跨域允许时间
}
}
2
3
4
5
6
7
8
9
10
11
12
# flutter端:
if (getx.GetPlatform.isWeb) {
//要用专门给浏览器写的adapter,并开启withCredentials
BrowserHttpClientAdapter adapter = BrowserHttpClientAdapter();
adapter.withCredentials = true;
//还需要 后台设置好cookie的跨域: SameSite=None; Secure
DioUtils.instance.dio.httpClientAdapter = adapter;
2
3
4
5
6
# 效果:
下一个请求携带cookie:
stat接口后台收到的cookie:
# 4 flutter插件适配
为防止某只支持Android/ios的插件在web上调用异常,要加await再捕获:
比如
try{
await _flipperNetworkPlugin.reportRequest(uniqueId,DateTime.now().millisecondsSinceEpoch,
'${options.baseUrl}${options.path}',options.method,headers,body);
}catch(e,s){
debugPrint("print by dio-flutter: reportRequest--> "+e.toString());
//+s.toString()
}
2
3
4
5
6
7
# 5 debug时外部访问localhost
flutter run -d chrome --web-hostname xxx.xxx.xxx.xxx --web-port xxxx
# 6 网页标题
https://juejin.cn/post/6967993169115873316
加一行:
Widget build(BuildContext context) {
SystemChrome.setApplicationSwitcherDescription(ApplicationSwitcherDescription(
label: '当前页面的标题',
primaryColor: Theme.of(context).primaryColor.value,
));
return Container(...);
}
2
3
4
5
6
7
8
9
10
# web in mobile时不显示appbar,避免重复的标题栏:
根据platformType进行判断即可,在全局配置中修改
# 7 路由和url path
https://www.jianshu.com/p/17c1984d8670 flutter web路由实践杂谈
默认是host/#路由,和vue一样,怎么处理成host/路由?
https://flutter.cn/docs/development/ui/navigation/url-strategies
Flutter Web 应用支持两种基于 URL 的路由的配置方式:
Hash(默认)
路径使用 # + 锚点标识符 (opens new window) 读写,例如:flutterexample.dev/#/path/to/screen
。
Path
路径使用非 # 读写,例如:flutterexample.dev/path/to/screen
。
目前flutter3.7.0推荐使用Hash模式. 不要再使用path模式. hash模式的带#一样可以分享出去直接访问. path模式有各种bug, 404,以及参数无法传递等等.
# 7.1 配置 URL 策略
让 Flutter 使用 path 策略,请使用 flutter_web_plugins (opens new window) 库中提供的 setUrlStrategy
(opens new window) 方法。
content_copy
import 'package:flutter_web_plugins/url_strategy.dart';
void main() {
usePathUrlStrategy();
runApp(ExampleApp());
}
2
3
4
5
6
此时,访问子路由会出现404,这时需要配置nginx:
# 7.2 nginx配置
原来是托管应用页面的 web 服务(例如 Nginx),默认会把多级路径的 URL 按目录去解析,访问最后一个目录下的 index.html 文件。而对于单页应用来说,显然后续的路径都是作为参数来使用的。而 Hash 模式由于使用一个井号把后续路径隔开了,自然就没有这个问题了。
通过在 Nginx 的配置文件中添加如下配置:
location / {
# ...
try_files $uri $uri/ /index.html;
}
2
3
4
5
意为:先尝试访问 URL 和其路径下的文件,如果不存在,则直接访问根目录的 index.html。
这样就能在线上部署环境顺利解析并传递参数了。
为什么开发的时候没有出现这个问题呢?原因就是在调试模式下,Flutter 优化了本地开发服务器,会优雅的处理各类 URL 策略并指向根目录的 Html 文件,也就是替我们做了上面这一步。
参考: https://juejin.cn/post/7167710704550543397
# 8 图片跨域
使用canvaskit时,图片的拉取不是用img标签,而是ajax请求,所以,使用html渲染器即可,imageview会被编译为img标签
flutter run -d chrome --web-renderer html
flutter build web --web-renderer canvaskit
flutter build web --web-renderer html # 这个命令
2
3
# 9 第一次白屏时间太长
Dart.js的瘦身后面再做,先加个炫酷的loading再说. 其实就是在index.html里贴个css动画
参考: CSS 3.0实现加载动画 (opens new window)
实际效果: https://navi.hss01248.tech/
可以直接改web模板:
# 10 输入框的一些问题
# 输入中文时残留首字母
参考: https://blog.csdn.net/m0_55782613/article/details/129329827
一、Bug样例
建立一个web demo flutter run -d chrome --web-renderer html 出现问题: 输入中文的时候,比如打字 hao, 第一个字母h会先输入,变成 h奥
二、解决
网上资料说是因为在text onChange中使用了setState刷新会打断输入,实际测试,就算不设置onChange, 不设置controller,一样会导致错误
最后将TextField替换为 TextFormField之后,问题得到解决
三、总结
这个问题在StackOverflow上面查不到相关解释,Flutter在Web上的兼容性问题还是存在的
# 字数统计异常
中文输入法统计正在编辑中文的过程中会统计英文,假如限制5个中文,当输入4个中文后,最后一个中文输入2个及以上英文时,会触发最大字数限制,这当然不是我们想要的效果。
下面说下如何修复这个问题,关键是 TextField 中 「controller.value.composing」 这个属性,官方文档说明:
❝The range of text that is still being composed. 仍在编写的文本范围。 ❞
TextEditingController _controller = TextEditingController();
int _wordLength = 0;
/// 计算字数,不算正在编辑的文字
void _computeWordCount() {
var valueLength = _controller.value.text.length;
var composingLength =
_controller.value.composing.end - _controller.value.composing.start;
setState(() {
_wordLength = valueLength - composingLength;
});
}
TextField(
controller: _controller,
onChanged: (value){
_computeWordCount();
},
decoration: InputDecoration(
counterText: '$_wordLength/32'
),
),
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 在html渲染引擎下支持不好的一些库:
# flutter_pulltorefresh (opens new window) :
不支持web pc 滚轮滚动
解决方案:
https://github.com/peng8350/flutter_pulltorefresh/issues/339
# lottie-flutter (opens new window):
pc的web和移动端的web兼容能力不同
https://github.com/xvrh/lottie-flutter/issues/206
解决方案: web下用其他动画实现