一個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評論。