一个Flutter项目接手记录

renyuneyun 2025年05月25日(周日) 2 mins

最近有需要在一个Flutter项目上继续开发,但我是第一次使用Flutter/Dart,这个项目最近一年也没有继续维护,所以在接手的过程中碰上不少有意思/烦人的事情。虽然没什么特别的技术含量,但还是记录一下,方便有需要的人参考。

简况

要接手的是一个研究项目的代码,由于没有开源,所以我就不放链接了。

该项目开发于2023年,之后没有继续维护,而且也没有留下说明文档。我又从来没有用过Flutter/Dart,所以接手过程也是同时学习工具链的过程。

我知道该项目是为了Android开发的,但不知道是否也在其他平台进行调试。接手过程中发现还用了Google的Firebase,于是也涉及了它相关的工具及问题。

在最初走了一点弯路以后,我使用了fvm来管理Flutter和Dart版本,还是比较顺利的——直接用fvm use选择合适的版本即可,会自动下载Flutter和对应的Dart版本。我通过时间进行推测,从比较早的Flutter版本(2023年的3.7.11)开始尝试,逐步迁移到更新不少的版本。中间很多都可以自动解决,尤其是通过dart fix可以解决由于Flutter和Dart版本变化而带来的一些改变(比如库中的参数名,变量类型),这还是挺好的(只是我个人始终有点对魔法的怀疑)。少部分时候在涉及编译到特定平台时,由于版本变化,需要手动调整配置,这个过程我也算熟悉了一下Flutter项目的构成:

  • 项目的核心代码存在lib/目录下;

  • 项目下还有一些平台相关的脚手架代码,位于各个平台名称的目录下(比如linux/);

    • 这些代码普遍是自动生成的,如果没有需要的话完全可以不管它们;

    • 随着Flutter版本更新,这些脚手架代码有时候也会变化,如果实在变化太大,可以删掉这些目录然后执行flutter create --platforms=linux .这样的命令来重新生成;

    • 如果进行了一些修改(比如我接手的项目在android上引入了额外依赖,也就是firebase),那么可能需要手动进行迁移了,不过官方文档写得还可以,虽然不完美但足够参考。

然而配置和试用下来有一些细节地方处理得还是不太好,有点糟心,报错也奇怪,解法也不见得好找;有时还有特定版本或特定平台的问题。

下面各节按问题呈现,没什么具体的理论讨论,只是呈现问题以及相关解决方法,并偶尔会有一些我的感受评价。

MacOS

由于工作机是MBP(参见《Linux用户的Mac初体验(及吐槽)》),最开始就在MacOS上进行配置,但发现MacOS及系统上的工具链本身会造成一些困难,也是挺难评的。

损坏的Dart SDK包

在MacOS上使用fvm安装工具链时,在下载Dart SDK的时候会出错(会输出到console),但脚本没有检测这个错误,而是继续执行,并且报成功。然而之后执行的时候会再次检测到没有安装成功,重复该步骤。具体输出如下:

Setting up Flutter SDK: 3.7.11

Downloading Darwin arm64 Dart SDK from Flutter engine 1a65d409c7a1438a34d21b60bf30a6fd5db59314...
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  217M  100  217M    0     0  10.2M      0  0:00:21  0:00:21 --:--:-- 10.6M
UnZip 6.00 of 20 April 2009, by Info-ZIP.  Maintained by C. Spieler.  Send
bug reports using http://www.info-zip.org/zip-bug.html; see README for details.

Usage: unzip [-Z] [-opts[modifiers]] file[.zip] [list] [-x xlist] [-d exdir]
  Default action is to extract files in list, except those in xlist, to exdir;
  file[.zip] may be a wildcard.  -Z => ZipInfo mode ("unzip -Z" for usage).

  -p  extract files to pipe, no messages     -l  list files (short format)
  -f  freshen existing files, create none    -t  test compressed archive data
  -u  update files, create if necessary      -z  display archive comment only
  -v  list verbosely/show version info       -T  timestamp archive to latest
  -x  exclude files that follow (in xlist)   -d  extract files into exdir
modifiers:
  -n  never overwrite existing files         -q  quiet mode (-qq => quieter)
  -o  overwrite files WITHOUT prompting      -a  auto-convert any text files
  -j  junk paths (do not make directories)   -aa treat ALL files as text
  -C  match filenames case-insensitively     -L  make (some) names lowercase
  -X  restore UID/GID info                   -V  retain VMS version numbers
  -K  keep setuid/setgid/tacky permissions   -M  pipe through "more" pager
See "unzip -hh" or unzip.txt for more help.  Examples:
  unzip data1 -x joe   => extract all files except joe from zipfile data1.zip
  unzip -p foo | more  => send contents of foo.zip via pipe into program more
  unzip -fo foo ReadMe => quietly replace existing ReadMe if archive file newer

It appears that the downloaded file is corrupt; please try again.
If this problem persists, please report the problem at:
  https://github.com/flutter/flutter/issues/new?template=1_activation.md


 Flutter SDK: SDK Version : 3.7.11 is setup
 Project now uses Flutter SDK : SDK Version : 3.7.11

这个错误和Flutter或者Dart版本无关,而是MacOS自带的unzip命令似乎有问题,会认为下载下来的.zip包损坏,无法解压。解决方法也很简单:通过包管理器安装个unzip,然后改用这个安装的unzip即可。

我是用homebrew安装的,装完会提示需要修改PATH,不然没法用。

我暂时放弃使用Nix做包管理器了,因为MacOS上似乎有很多复杂的细节,缺少相关文档/文章解释要怎么处理。反正这是工作机,只有我自己使用,所以homebrew虽然有多用户的权限问题,但对我来说不算问题。只不过homebrew执行命令时每次都要自己更新这事实在烦人。

另外,比较好玩的是在Finder中解包下载下来的.zip包是没有问题的。所以我很不理解MacOS上的zip过程究竟是通过什么完成的,莫非是两个不同团队分别做了一份实现?

找不到Android SDK

由于需要编译Android的包,需要安装Android SDK。我选择通过homebrew安装官方的android-commandline,用其提供的sdkmanager来管理。这部分都挺顺利的,我也安装了相关版本的platform和build tools。

然而在Flutter编译时,会报告无法找到相应的Android SDK。似乎Flutter不能正确识别。

通过为Flutter手动设置Android SDK位置可以解决:

flutter config --android-sdk /opt/homebrew/share/android-commandlinetools

Java版本

做过Android开发会知道编译时需要特定(低)版本的Java,否则编译通不过。然而我系统全局使用的是几乎最新的版本,编译时会出问题。

解决方法是为项目设置Java版本。如果是较新版本Flutter的话(比如3.13.0),这个设置可以通过Flutter进行:

flutter config --jdk-dir <path to jdk 15>

而如果是较早版本的Flutter,则没有--jdk-dir这个参数,不能这么做,而只能通过在gradle.properties中增加Java目录来解决(参考):

org.gradle.java.home=<path to jdk 15>

至于Java Home怎么找,则可以通过下面这个命令列出本机已经安装的所有环境,然后挑自己想要的(参考):

/usr/libexec/java_home -V

XCode SDK错误

我从来没有开发过Mac软件,所以在尝试编译成Mac程序时碰到了错误:

flutter macos xcrun: error: unable to find utility "xcodebuild", not a developer tool or in PATH

这个问题是因为我没有安装XCode,本来应该是很好解决的:去App Store安装就好了。

然而,由于我的系统是上一个版本,一直没有更新到最新,但App Store里的XCode只支持最新版本的系统,安装不了。

解决方法是手动下载XCode的历史版本,然后手动安装并部署。参考这里可以寻找到相关工具的历史版本,不过需要人工在页面中查找一下。

这里还是要稍微夸一下苹果的。虽然更好的解决方法是在App Store中提供相应版本的软件——毕竟我这只是差了一个版本,又不是上古版本。

通用问题

构建Android apk

在早期版本的Flutter工具中,似乎没有明确写怎么通过命令行构建特定平台的包。而且官方文档也写得不清不楚的,通篇都在写怎么构建release版本(需要配置自己的key),而不提怎么构建debug版本(不需要配置key)。

后来参考这个文章,猜出下面命令可以构建:

flutter build apk --debug

另注,较新版本的Flutter的flutter build命令的帮助写得清晰多了,直接告诉你可以flutter build apk。但其实它还是没说debug build和release build的信息。

Firebase配置失败

我按照官方说明和其他人的文章去配置Firebase,但在执行flutterfire configure时始终不成功,会报形如这样的错误:

i Firebase android app xxx registered.                                                                                                                                               
i Firebase ios app xxx registered.                                                                                                                                               
i Firebase macos app xxx registered.                                                                                                                                             
i Firebase web app xxx (web) registered.                                                                                                                                                     
type 'Null' is not a subtype of type 'String' in type cast

在网上找到很多人也有这些报错(如[1][2]),但对解法都莫衷一是。我最初是发现它是和特定firebase目标平台有关(也就是web和windows),只要配置时不选中它们就可以正常执行结束,生成配置文件。

后来偶然发现,似乎是Dart版本问题:我在更新到Dart 3.3.4(Flutter 3.19.6)后,就再也不会出这个问题了。我也发现了这个SO问题,一直没人提到版本,所以我也凑了个热闹,去添了条评论。

顺便喷一下firebase:它不支持linux作为部署目标平台,不知道是为什么。

库问题

一些库会导致构建出错,比如我这里用到了video_compress,在编译(apk)时会导致报下面这个错误:

 Could not find com.otaliastudios:transcoder:0.9.1.

这个问题早就有人报告了,一个临时性解决方法是在pubspec.yml中改用如下依赖声明:

  video_compress:
    git:
      url: https://github.com/SpectoraSoftware/VideoCompress
      ref: caf41183e84adebd6ef5c8fba547937daf556a65 # Or master?

另外,后来在更新到Flutter 3.22后,这个声明似乎也不再需要。但我不太理解为什么在更早的Flutter版本时会需要这句。

Dart和依赖版本

最后说个大的问题吧。我在配置的过程中发现,Dart的依赖和版本检测似乎挺成问题的,至少和直觉对不上号。

简单来说,它似乎可以自动进行下面的检测:

  • (在fvm那)根据你配置的Dart SDK版本(sdk:)判定要安装的Flutter版本是否合适当前项目;

  • 根据你配置的sdk版本,判定新增依赖是否可以使用;

  • 检查各包间的版本依赖,并判断最新可以升级到哪个版本。

然而, 下面的这些是很成问题的:

  • 配置中写了sdk版本,但flutter pub upgrade更新到的版本会因为用了更新的语言特性而导致编译不过;
  • 仓库中(pub.dev)的库声明的最低Dart版本有的不正确。

注意这其实是两个问题:1. 虽然声明了版本,但本地的Dart/Flutter工具链不知道因为什么不能正确判定可行版本;2. 仓库中的版本兼容性声明有问题。

第一点是工具本身的问题,我暂时没有发现规律,但在我的尝试中出现过。第二点则是维护的问题,不完全是语言本身的错误。然而我之所以对它们有很大意见,是因为有许多包都存在这种版本兼容性声明错误的问题,甚至包括谷歌自己的包(firebase)。这两点共同导致我在升级依赖时反复尝试,花了数个小时才解决。

这就让我不太理解了,毕竟语言特性这事很容易检查,第三方的包维护有问题也就算了,谷歌自己的包也有问题算是哪门子的事?难道谷歌firebase、flutter或者dart团队对发布包时没有兼容性质量检查环节?

再稍微延伸一下,其实很多语言都有这个问题:仓库中声明的兼容版本未必是真实的。许多人通过各种CI工具自动检查不同版本的依赖,所以原则上也许可以通过这种方式检查所有的?甚至更进一步,只是检查编译型语言特性的话,资源消耗应该比较小,也许仓库可以提供相关检查?

总结与感受

目前感受来说,Flutter设计有点意思,在跨平台的开发上似乎有不少的巧思,且由于显式地暴露个平台的脚手架代码,与已有生态有相当的兼容度(虽然代价是迁移时比较麻烦)。

但它的不少细节设计给我一种放任自流的感觉,并没有良好解决;另外还有一部分则由明显的公司业务倾向驱动,比如web构建执行只会找chrome(是的,甚至不含chromium),而且由于最近firefox不打算支持chrome的调试接口,现在有了更冠冕堂皇的不支持firefox的理由了(参见这个issue)。

我是很喜欢它的跨平台性的。当初那个年代需要开发额外的一门语言似乎也有一定道理。但我对Dart的一个设计深表不满:import会导入目标的所有符号到当前命名空间,而不是将目标作为一个命名空间导入。默认行为会鼓励使用,这应当是众所周知的事情。而这种使用为重构和查错带来了大量麻烦,让人实在是不能理解为什么要这么设计。

总之,我会继续在这个项目上使用Flutter和Dart,希望之后可以给我带来更多的舒适而非目前碰到的各种复杂。


Related posts:

您可以在Hypothesis上的該群組內進行評論,或使用下面的Disqus評論。