最近有需要在一個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,希望之後可以給我帶來更多的舒適而非目前碰到的各種複雜。
您可以在Hypothesis上的該群組內進行評論,或使用下面的Disqus評論。