前言 笔者最近了解了Flutter web相关的内容,本文会分享创建Flutter web项目、Flutter web项目预览,Flutter web项目和Flutter mobile(Flutter Android/iOS)项目的差别、搭建简易Dart服务器(解决跨域问题)、上线Flutter web项目相关内容。
一、创建Flutter web 项目
准备Flutter web 环境
更新本地环境为 beta channel最新版。(dev channel 也可以)
flutter channel beta
flutter upgrade
Flutter有如下4个channel:
flutter channel
Flutter channels:
beta
* dev
master
stable
Flutter 官方建议使用 stable 的channel。
master 是当前最新的channel;
dev 是当前最新的充分测试后的channel;
beta是每个月Flutter官方调整选出来的最好的dev的channel,并提升为beta channel;
stable是Flutter 认为是当前最稳定的channel。
稳定性而言:master < dev < beta < stable 。更多内容可查看:Flutter build release channels
开启项目支持Flutter web
flutter config --enable-web
如果想在当前已有项目Flutter mobile项目的基础上,添加Flutter web支持,可 cd 到 Flutter mobile 项目目录下,添加Flutter web支持。
新建Flutter web项目
如果之前没有创建过Flutter 项目,新建一个Flutter web项目可以使用如下命令。
flutter create 项目名(小写)
如:flutter create qi_flutter_web_demo
现有项目生成Flutter web 相关文件
如果之前创建过Flutter 项目,想现有项目生成web文件夹及index.html等文件可使用如下命令。
flutter create .
运行项目命令:flutter run -d chrome
遇到问题:运行失败
运行失败报错如下:
wangyongwangdeiMac:qi_flutter_page wangyongwang$ flutter run -d chrome
Flutter assets will be downloaded from https://storage.flutter-io.cn. Make sure you trust
this source!
Downloading Web SDK... 1.1s
Launching lib/main.dart on Chrome in debug mode...
Error compiling dartdevc module:qi_flutter_page|lib/main_web_entrypoint.ddc.js
packages/qi_flutter_page/main_web_entrypoint.dart:9:18: Error: Too few positional arguments:
1 required, 0 given.
entrypoint.main();
^
AssetNotFoundException: qi_flutter_page|lib/main_web_entrypoint.ddc.js
Failed after 23.3s
Building application for the web... 33.5s
Failed to build application for the Web.
猜测原因:访问网址https://storage.flutter-io.cn.不可达
起初,笔者猜测原因是这个网址https://storage.flutter-io.cn.访问不可达;不过试过运行新创建的Flutter web项目,发现新建的Flutter web项目可以正常运行,可以排除问题不在于网址https://storage.flutter-io.cn.访问不可达。
继续看这段报错,可以发现Flutter web 项目的main 方法中不能有参数。
packages/qi_flutter_page/main_web_entrypoint.dart:9:18: Error: Too few
positional arguments: 1 required, 0 given.
entrypoint.main();
^AssetNotFoundException: qi_flutter_page|lib/main_web_entrypoint.ddc.js
Failed after 22.9s
问题在于main方法中参数
运行Flutter web 项目的时候,main方法中不能有参数。
void main(List args) {
}
// 删除main方法名中的参数后,可以正常运行。
void main() {
}
笔者以之前写的项目qi_flutter_page为例:运行起来的效果如下:
上周和同事CH聊天学到的内容:Flutter web项目显示的网页的特点:
显示网页源代码的时候,可以网页发现显示的内容是html的body 中嵌套的main.dart.js。
二、Flutter web 项目预览
运行Flutter web项目 默认会在Chrome浏览器中显示,不过在本机的Safari 浏览器中及模拟器中的浏览器中输入相应的网址,也可以显示相应的视图。
三、学习Flutter for web开发者(插曲)
笔者参照Flutter for Web开发者教程上做了如下尝试。
color: Colors.black12, // black 12%透明度
color: Colors.grey[300], // 300: Color(0xFFE0E0E0),
color: Color.fromRGBO(r, g, b, opacity),
color: Color.fromARGB(a, r, g, b), // a 为0的时候完全透明 255 完全不透明
四、Flutter web项目 与 Flutter mobile 项目的不同
笔者在把现有Flutter mobile项目,直接支持Flutter web 的过程中遇到了网络请求报异常的问题,另外简单测试了2个三方库的在Flutter web项目中的体现。
HttpClient() 不能用于Flutter web 项目
try {
HttpClient client = HttpClient();
} catch (e) {
print('捕获异常:$e');
}
捕获异常:NoSuchMethodError: invalid member on null: 'indexOf'
Flutter For Web 的网络请求可以使用html.httpRequest。
import 'dart:html' as html;
html.HttpRequest.request(url).then((responseValue) {
});
三方库支持情况
笔者这里举2个自己使用过的2个三方库,shared_preferences、url_launcher均支持 Flutter web 项目。
url_launcher 5.4.1支持 Flutter web 项目。
shared_preferences 支持 Flutter web 项目。
下列代码对于Flutter Web 项目中仍然支持打开加载url的窗口。
String soUrl = 'https://www.so.com';
if (await canLaunch(soUrl)) {
await launch(soUrl);
}
由如下代码及相应结果可知,shared_preferences 也支持 Flutter Web 项目。
void _incrementCounter() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
int counter = (prefs.getInt('counter') ?? 0) + 1;
print('Pressed $counter times.');
await prefs.setInt('counter', counter);
}
ListTile2
Pressed 1 times.
ListTile2
Pressed 2 times.
ListTile2
Pressed 3 times.
ListTile2
Pressed 4 times.
ListTile2
Pressed 5 times.
三方库一般会注明支持的平台。(Android、iOS或Web)
如下图所示:
五、简易Dart服务器
使用如下代码,可以本地启动一个Dart服务。
main() async {
var server = await HttpServer.bind(InternetAddress.loopbackIPv4, 9988);
await for (var request in server) {
request.response
..headers.contentType = ContentType('text', 'plain', charset: 'utf-8')
..write('Hello Dart! 你好Dart')
..close();
}
}
浏览器中直接请求http://127.0.0.1:9988 示意图如下:
笔者在Flutter web项目中请求,http://127.0.0.1:9988的时候,遇到了跨域问题,下边分享下相关问题及处理方式。
跨域问题
跨域问题描述
跨域资源共享(CORS) 是一种机制,它使用额外的 HTTP 头来告诉浏览器 让运行在一个 origin (domain) 上的Web应用被准许访问来自不同源服务器上的指定的资源。当一个资源从与该资源本身所在的服务器不同的域、协议或端口请求一个资源时,资源会发起一个跨域 HTTP 请求。
比如,站点 http://domain-a.com 的某 HTML 页面通过 的 src 请求 http://domain-b.com/image.jpg。网络上的许多页面都会加载来自不同域的CSS样式表,图像和脚本等资源。
出于安全原因,浏览器限制从脚本内发起的跨源HTTP请求。 例如,XMLHttpRequest和Fetch API遵循同源策略。 这意味着使用这些API的Web应用程序只能从加载应用程序的同一个域请求HTTP资源,除非响应报文包含了正确CORS响应头。 引自HTTP访问控制(CORS)
举2个例子比如我们自身当前域名为abc.com, 访问def.com 会出现跨域的问题。
比如我们自身当前域名为abc.com端口号为1234(abc.com:1234),那么访问abc.com:5678也会出现跨域问题。
笔者使用Flutter Web 请求服务端资源的时候遇到了跨域问题。
http://localhost:55355/#/ 中的内容访问http://127.0.0.1:9988
Flutter Web跨域现象图现象图如下:
出现当前跨域问题的原因是端口号不同,访问Flutter Web 的url 和 请求服务端资源的url的 端口号
不同。
请求的响应头中设置可跨域的origin,解决跨域问题
设置跨域的url 有2种设置方式:
- 1.设置一个或多个url;
- 如:request.response
..headers
.add('Access-Control-Allow-Origin', request.headers['origin'])
- 如:request.response
- 2.设置跨域的值为*;
..headers.add('Access-Control-Allow-Origin', '*')
注意:如果在本地测试使用,可以使用第二种方式,直接了当。但是一般线上的话最好使用第一种方式设置是否可以跨域请求。因为设置是否可以跨域,算是服务器在响应浏览器请求数据时的一种保护策略。
其中重点是在响应头中添加可以跨域的请求域。
..headers.add('Access-Control-Allow-Origin', 'http://localhost:55355')
如'Access-Control-Allow-Origin'可以指定特定的url,使url能够跨域请求。
如有需要指定允许多个url进行跨域请求。可以根据请求的origin的值,判断是否要做跨域响应头的处理。
如:如下代码设置了当请求的origin 为http://localhost:63062
或 http://localhost:55355
的时候,会添加跨域处理的响应头。
main() async {
var server = await HttpServer.bind(InternetAddress.loopbackIPv4, 9988);
var server = await HttpServer.bind(InternetAddress.loopbackIPv4, 9988);
await for (var request in server) {
var accessControlAllowOrigin = [
'http://localhost:63062',
'http://localhost:55355'
];
if (request.headers['origin'] != null) {
for (String tempAllowOrigin in accessControlAllowOrigin) {
if (request.headers['origin'].first.contains(tempAllowOrigin)) {
request.response
..headers
.add('Access-Control-Allow-Origin', request.headers['origin'])
// ..headers.add('Access-Control-Allow-Origin', '*')
..headers.contentType =
ContentType('text', 'plain', charset: 'utf-8')
..write('Hello Dart! 你好Dart 跨域')
..close();
}
}
} else {
request.response
..headers.contentType = ContentType('text', 'plain', charset: 'utf-8')
..write('Hello Dart! 你好Dart 不需要跨域')
..close();
}
}
}
笔者在上边说明了处理运行Flutter web项目的时候,处理本地服务端接口和Flutter web项目运行网址出现跨域问题的处理方式。(其实对于编译后的Flutter web项目的产物直接放到自己的服务端项目的的静态文件目录下的时候,不会出现上述问题)
有时候我们的请求内容可能就需要跨域去请求数据,而且对方如果也不便添加相应的跨域响应头。此时,可使用Nginx 做反向代理来处理跨域问题。
Nginx 反向代理解决远端跨域问题
server {
listen 9080;
server_name localhost;
location ~ /columns/Qtest {
proxy_pass https://testerhome.com;
}
}
经上述处理,可以在本地的127.0.0.1:9080/columns/Qtest请求到 Qtest测试之道的 https://testerhome.com/columns/Qtest 相应数据。
Nginx 配置反向代理及rewrite访问路径可实现访问远端文件不跨域。
location / {
proxy_pass https://weekly.75team.com;
}
location ~ /api/qiwuzhoukanWeb {
rewrite /api/qiwuzhoukanWeb /;
proxy_pass https://weekly.75team.com;
}
经上述处理,可以在本地的127.0.0.1:9080/api/qiwuzhoukanWeb请求到 奇舞周刊的 https://weekly.75team.com 相应数据。
六、Flutter Web 项目上线
flutter build web
会在项目的build 目录中生成相应的资源文件及html 和js文件,把相关文件放置到服务端静态文件目录下即可。实现上线Flutter Web项目。
参考学习网址
- Flutter build release channels
- 使用 Flutter 构建 Web 应用
- HTTP访问控制(CORS)
- 奇舞周刊
- Qtest 测试之道
- 详解 CORS 跨域资源共享
- 跨域资源共享 CORS 详解
- proxy_pass url 反向代理的坑