Flutter-升級Flutter到3.0版本

前言

目前項目已經在2.x的版本上運行一段時間了,截止到目前Flutter穩定版本已經到3.7.0了,
但是Flutter3.0.x是最后支持iOS9、iOS10以及32位系統的版本,所以基于各方面考慮,決定把Flutter升級到3.0.5版本。
同時,因為空安全也已經出來很久了,且在dart 2.19版本后,可能不支持空安全遷移工具了,所以決定把項目也遷移到空安全。

主要兩步:

  1. 升級Flutter版本,文檔入口
  2. 升級依賴庫版本至空安全版本,并遷移代碼文檔入口

準備工作

  1. 使用命令dart --version查看dart版本
kaye@KKdeMacBook-Pro app-flutter % dart --version
Dart SDK version: 2.12.2 (stable) (Wed Mar 17 10:30:20 2021 +0100) on "macos_x64"
  1. 使用命令flutter doctor查看flutter版本
kaye@KKdeMacBook-Pro app-flutter % flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[?] Flutter (Channel stable, 2.0.4, on macOS 11.5.1 20G80 darwin-x64, locale zh-Hans-CN)
[?] Android toolchain - develop for Android devices (Android SDK version 29.0.2)
[?] Xcode - develop for iOS and macOS
[?] Chrome - develop for the web
[?] Android Studio (version 2020.3)
[?] VS Code (version 1.62.2)
[?] Connected device (1 available)

? No issues found!

升級Flutter版本

//升級到支持的最新版本
flutter upgrade 
//如果你想指定版本,則在升級后可以進入到flutter的安裝目錄進行重置
//HEAD^就是你想要的版本的commit id
git reset --hard HEAD^ 

升級依賴三方庫

  1. 使用命令dart pub outdated --mode=null-safety查看當前依賴的pakeage是否支持空安全。
    項目中所依賴的三方庫,有一部分沒有支持空安全,如那些版本前面有x的三方庫,而那些打的就是已經支持的空安全版本。
kaye@KKdeMacBook-Pro app-flutter % dart pub outdated --mode=null-safety
Showing dependencies that are currently not opted in to null-safety.
[?] indicates versions without null safety support.
[?] indicates versions opting in to null safety.

Package Name         Current  Upgradable  Resolvable  Latest   

direct dependencies:
charts_flutter       ?0.9.0   ?0.9.0      ?0.11.0     ?0.12.0  
flutter_swiper       ?1.1.6   ?1.1.6      ?1.1.6      ?1.1.6   
keyboard_visibility  ?0.5.6   ?0.5.6      ?0.5.6      ?0.5.6   

1 dependency is constrained to a version that is older than a resolvable version.
To update it, edit pubspec.yaml, or run `dart pub upgrade --null-safety`.
  1. 升級依賴庫
dart pub upgrade --null-safety

如果你的依賴庫全部支持空安全,這里會將所有依賴升級到空安全中,如果不是全部支持,命令行中會打印很多支持依賴的三方庫,只需要將建議運行的命令拷貝并在命令行中運行即可。至于那些一直不支持空安全的三方庫,需要考慮更換別的庫進行代替了。

啟動遷移

執行啟動遷移命令, 如果在使用命令過程遇見了錯誤,可根據提示,參考下面的命令

//直接遷移
dart migrate
//跳過依賴的三方庫是否支持空安全
dart migrate --skip-import-check 
//跳過依賴的三方庫是否支持空安全且忽略異常情況
dart migrate --skip-import-check --ignore-exceptions 

執行命令,開始分析需要進行遷移的代碼

kaye@KKdeMacBook-Pro app-flutter % dart migrate
Migrating /Users/xxxx/app-flutter

See https://dart.dev/go/null-safety-migration for a migration guide.

Analyzing project...
[--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------]No analysis issues found.

Generating migration suggestions...
[--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------]

Compiling instrumentation information...
[--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------]

View the migration suggestions by visiting:

  http://127.0.0.1:61098/xxxx/app-flutter?authToken=S12X5Ez93Iw%3D

Use this interactive web view to review, improve, or apply the results.
When finished with the preview, hit ctrl-c to terminate this process.

If you make edits outside of the web view (in your IDE), use the 'Rerun from
sources' action.
  • 左側部分:是建議支持空安全的文件,選中每個文件會在中間展示這個文件的所有變化后的代碼,如果你不想遷移某個文件,可以取消勾選,具體請看后面的文章,有介紹
  • 中間部分:是讓我們看所有變化后的代碼,我們可以在上面進行確認修改
  • 右側部分:是相關個改變的結果詳細注解,點擊linexxx,下面的Edit Details可以看具體的原因
可視化工具.png

從上圖我們可以看到,高亮部分,如late、?、!之類的都是遷移工具經過分析之后,給我們自動添加上去的,但是通過工具推導出來的類型也可能是錯誤的,我們就需要對遷移結果進行改進。下面,通過一個例子來說明。

遷移順序

遵循一個原則,從依賴最少的文件開始遷移。
如:A依賴B,B依賴C,C不依賴別的文件,那么遷移順序就是C、B、A。

改進遷移建議

  • 原來非空安全的代碼
var intList = const <int>[0, null];
var zero = intList[0];
var one = zero + 1;
var zeroOne = <int>[zero, one];
  • 遷移工具遷移為空安全的結果
var intList = const <int?>[0, null];
var zero = intList[0];
var one = zero! + 1;
var zeroOne = <int?>[zero, one];

從上面的結果,我們可以看到 ,遷移工具認為zero變量的類型為int?,但是其實我們知道,這種情況下,zero不可能為null,且zeroOne中的元素也不可能為null,所以我們需要對遷移結果進行改進。此時我們還沒有應用遷移結果,所以我們仍然可以在IDE中修改我們的代碼,然后點擊遷移界面右上角RERUN FROM SOURCES按鈕進行預估遷移結果刷新。

因為在此時我們還沒有完全支持空安全,所以在代碼中,無法使用late、?、!這些關鍵字,如果你使用了,那么開發工具爆紅,如下圖

錯誤提示.png

那么我們怎么在非完全空安全的情況下,進行標記呢?官方給我們提供了一些表達式,供我們使用。

表達式 說明
expression /*!*/ 添加 ! 至代碼中,將 表達式 轉換為其基礎類型對應的不可空的類型。
type /*!*/ 類型 標記為非空。
/*?*/ 將前面的類型標記為可空。
/*late*/ 將變量聲明標記為 late,表示其不會第一時間進行初始化。
/*late final*/ 將變量聲明標記為 late final,表示其不會第一時間進行初始化,且初始化后不可改變。
/*required*/ 將參數標記為required

所以,我們可以在我們的代碼中使用/*?*/或者/*!*/等進行標記。

var intList = const <int>[0, null];
var zero = intList[0]/*!*/;
var one = zero + 1;
var zeroOne = <int>[zero, one];

點擊遷移界面右上角RERUN FROM SOURCES按鈕進行預估遷移結果刷新,結果如下:

改進后的結果.png

可以看到,因為在第2行末尾添加了/!/,導致遷移工具給出的遷移結果也是不一樣的。

//原始代碼遷移結果
var intList = const <int?>[0, null];
var zero = intList[0];
var one = zero! + 1;
var zeroOne = <int?>[zero, one];


//添加標記改進后遷移結果
var intList = const <int?>[0, null];
var zero = intList[0]/*!*/;
var one = zero + 1;
var zeroOne = <int >[zero, one];

應用遷移

如果我們覺得遷移結果沒有什么問題,那么點擊瀏覽器中遷移界面右上角APPLY MIGRATION就可以應用遷移結果,這個操作是不可逆的,所以我們需要確保完全認同遷移結果了,才可以點擊。
注意:應用遷移后會修改pubspec.yaml文件中的environment sdk版本

image.png

點擊確定,再看我們的代碼,此時已經應用了空安全。
控制臺輸出哪些文件遷移到了空安全,哪些不是空安全。

Applying migration suggestions to disk...
Migrated 8 files:
    test/widget_test.dart
    lib/turn_box.dart
    lib/main.dart
    lib/my_process_bar.dart
    lib/my_painter.dart
    lib/demo.dart
    pubspec.yaml
    .dart_tool/package_config.json
Opted 1 file out of null safety with a new Dart language version comment:
    lib/demo2.dart

pubspec.yaml中修改結果

//遷移前的版本
environment:
  sdk: '>=2.10.0 <3.0.0'


//遷移后的版本
environment:
  sdk: '>=2.12.0 <3.0.0'

代碼分析

遷移到空安全后,我們使用代碼分析器,對代碼進行分析,幫助我們進一步修改代碼,比如有些變量從可空變成了非空,如果我們在代碼中又判斷了是否為空,就顯得有些不必要。
使用dart analyze命令進行代碼分析,控制臺也給出了相應的提示,逐一修改即可。
當然,在Flutter開發中,如果我們使用了Android Studio的話,就可以直接使用可視化工具進行dart analyze如下圖所示,雙擊就可以定位到相應的位置,按需修復即可。

dart analyze.png

如果你不想遷移某些包

在使用遷移工具遷移項目時,有可能因為項目巨大,一次性無法完全遷移,那么可以直接取消勾選,這樣這些文件就不會被遷移,同時會在你的文件中頭部插入一行類似下面的注釋,這樣你的文件就不會應用空安全。后續如果想遷移到空安全,就再次執行命令dart migrate即可。

// @dart=2.9
// 如果不想應用空安全,可以添加上面的代碼

var intList = const <int>[0, null];
var zero = intList[0];
var one = zero + 1;
var zeroOne = <int>[zero, one];
再次啟用遷移工具

過程中遇見的問題

遷移空安全常見問題列表

Q1. 第三方庫不支持空安全,導致dart migrate命令執行錯誤

解決方案,執行命令 dart migrate --skip-import-check`

運行遷移命令.png
Q2. 代碼異常Null check operator used on a null value at offset

解決方案:找出相應的錯誤代碼所在文件,刪除?
或者執行dart migrate --skip-import-check --ignore-exceptions

kk@dabaodeMacBook-Pro app-flutter % dart migrate --skip-import-check
Migrating /Users/qgg/workspace/app-flutter

See https://dart.dev/go/null-safety-migration for a migration guide.

Analyzing project...
[-------------------------------------------------------------------------------------------------------------------------------------------------------------------------|]Warning: package has unmigrated dependencies.

Continuing due to the presence of `--skip-import-check`.  To see a complete
list of the unmigrated dependencies, re-run without the `--skip-import-check`
flag.

No analysis issues found.

Generating migration suggestions...
[-------------------------|                                                                                                                                                ]Aborting migration due to an exception.  This most likely is due to a
bug in the migration tool.  Please consider filing a bug report at:

https://github.com/dart-lang/sdk/issues/new
Please include the SDK version (2.17.6) in your bug report.

To attempt to perform migration anyway, you may re-run with
--ignore-exceptions.

Exception details:

Null check operator used on a null value at offset 4675 in /Users/qgg/workspace/app-flutter/lib/widget/trade/aip/aip_trade_target_rate_widget.dart (Offset?.zero)

#0      EdgeBuilder._handlePropertyAccessGeneralized (package:nnbd_migration/src/edge_builder.dart:3186:46)
#1      ....
......

Q2. FlutterBoost 4.x返回值的問題

在使用FlutterBoost進行push頁面的時候,如果使用await或者then獲取pop的返回值時,FlutterBoost對返回值進行了調整,4.x版本目前返回的結果如果沒有指定的話,返回的是一個空Map,在接收的時候要格外小心,除非返回的是Map類型,否則不要寫明類型,不然會拋出一個類型錯誤。具體在FlutterBoostApp._completePendingResultIfNeeded中體現,boost官方推薦返回值使用Map

case 1: BoostNavigator打開,result是個空的Map,如果指定了類型接收result,非Map類型可能會拋出類型錯誤的異常
A 頁面:
var result = await BoostNavigator.instance.push(xxx, withContainer: true); 
B 頁面:
BoostNavigator.instance.pop(); //這里沒有傳入pop的值

case 2: BoostNavigator打開,正常
A 頁面:
bool result = await BoostNavigator.instance.push(xxx, withContainer: true); 
B 頁面:
BoostNavigator.instance.pop(true); 

case 3:系統 Navigator打開,result可以是任何類型,包括null
A 頁面:
var result = await Navigator.push(context, xxx);
B 頁面:
Navigator.of(context).pop((bool/int/xxx); 或者
BoostNavigator.instance.pop(bool/int/xxx);非null值時,是可以指定具體的類型
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容