Flutter For Web 踩坑日记

2,553 阅读5分钟

前段时间用Flutter For Web撸了一个公司内部使用的项目,本篇文章就把开发过程中碰到的坑点记录一下。

1.环境配置

Flutter For Web的开发需要将Flutter SDK升级至任意一个非Stable版本即可,升级完成后需要开启相关配置。

  • 首先执行flutter config

什么都没开启过的同学跟我上图的状态应该是一致的,--enable-web就是开启web开发的,同理 --enable-macos-desktop 就是开启MacOS的,这里使用MacOS的小伙伴我推荐一并把MacOS的权限打开,因为后面有个坑需要用MacOS来填,不是MacOS的也不用紧张,这个坑只是会影响开发效率。

成功开启之后

此时执行flutter devices,即可看到对应的web设备 对应的代码编辑器需要大退重新启动就能选择对应设备

有些同学会遇到执行了--enable-web 去执行flutter devices依旧看不到对应平台的设备,并且执行flutter config 看到后面会多出来(Unavailable)标志当前设置为失效状态.

最后检查出原因:虽然我与团队内部的另外一个小伙伴都为1.17.3的版本,但是我是用的github上面的标签版本,而他是从Flutter官网下载的。官网下载的压缩包为当时版本的一个快照,后续如果更新版本并不会移除Stable channel的标记,但是github是实时更新的。

他的版本执行flutter doctor会有Stable channel的标记

但是从github上面Clone的项目执行出来会变成unknown

2.开发

Flutter For Web本身用起来跟Flutter差距不大

2.1 网络请求

身为一个老移动端开发,从一开始就没想到会在网络请求这里栽跟头,Web的跨域问题属实在初期给我愁的焦头烂额。有条件的前端同学可以直接让后端兄弟在接口请求头里添加 Access-Control-Allow-Origin ,也是非常舒服的。奈何我司分了相当多的领域,跟网关域的交涉并不顺畅,他们也有他们的难处,让本不富裕的时间雪上加霜。

最后搜集了各方面的资料,分为下面两种场景:

2.1.1 本地调试

  • 本地调试相对容易一些,首先最简单的办法就是ip直连,比较粗暴但是简单有效。

  • 第二种是一种比较取巧,也是我个人比较推荐的一种。在Flutter SDK如下位置添加 '--disable-web-security',

然后删除下面两个文件,之后执行flutter doctor,等待重新编译出新的flutter_tools的可执行快照,直接flutter run -d chrome。就是这种随心所欲的感觉,芜湖,起飞!

这种方式仅适用于本地启用的项目! 这种方式仅适用于本地启用的项目! 这种方式仅适用于本地启用的项目!

  • 第三种就是本地自己搭建一个Web服务,然后通过代理服务完成请求,这种方式是最通用的方式,现在能搜到的办法大多数是这种。笔主采用的是Nginx。关于如何本地搭建Nginx这个问题,笔主与百度、Google等各大搜索门户建立了深入的合作,直接去搜索就行。

配置好本地服务,监听对应的启动端口即可。这时候肯定就会有小伙伴发问了,每次启动的时候发现端口都是随机的,Nginx该怎么配置监听的端口号呢。对于这个问题我只能说,问得好~

在我们执行flutter run -d Chrome时是可以添加参数的。我们直接在后面添加--web-port=* --web-hostname=*,或配置在代码编辑器里配置

VSCode

Android Studio

2.2 调试

这个部分我暂时没有很好的方案,FFW的调试有点惨不忍睹。正常编译器的在FFW的运行时断点速度有亿点点慢,对于这方面在调试UI时我暂时使用的是Flutter For Desktop。

部署之后只能通过print的形式进行输出。(PS:大佬有好的办法可以指教一下,小弟感激不尽)

2.3 交互

这里交互指跟其他项目如何交互。笔主开发的FFW项目是通过iframe的形式嵌入到公司已有的后台管理系统。目前用到的方式是通过 postmessage 的形式进行互相通信的。具体的通信代码如下:

FFW侧

import 'package:bot_toast/bot_toast.dart';
import 'package:flutter/material.dart';
// ignore: avoid_web_libraries_in_flutter
import 'dart:html' as html;
import 'dart:convert';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      builder: BotToastInit(),
      navigatorObservers: [BotToastNavigatorObserver()],
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  html.MessageEvent _nativeEvent;
  html.MessageEvent get nativeEvent => _nativeEvent;
  set nativeEvent(html.MessageEvent event) {
    _nativeEvent = event;
    _nativeEvent.ports.first.onMessage.listen((html.MessageEvent event) {
      Map eventData = jsonDecode(event.data);
      String type = eventData['type'] ?? "";
      BotToast.showText(text: "Port2收到消息:type = $type");
    });
  }

  void _incrementCounter() {
    setState(() {
      if (nativeEvent != null) {
        nativeEvent.ports.first.postMessage("我是Flutter Port2");
        BotToast.showText(text: "port2已发送消息");
      }
    });
  }

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    html.window.onMessage.listen((html.MessageEvent event) {
      nativeEvent = event;
      Map eventData = jsonDecode(event.data);
      String type = eventData['type'] ?? "";
      BotToast.showText(text: "收到消息:type = $type");
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        // Here we take the value from the MyHomePage object that was created by
        // the App.build method, and use it to set our appbar title.
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

vue侧 因为涉及到太多公司业务代码,这里我就先不贴了,后续整理出一个最小demo再贴出来,大家也可以自行去Google web项目postmessage。

3.上线

3.1 部署

这个部分不多讲,执行Flutter build web --release,等待完成之后,在根目录build文件夹中找到web,剩下的就是正常web项目部署。

结尾

后续在阅读遇到什么问题希望大家可以帮忙指正一下,在此感激不尽。