将Flutter和Firebase应用程序迁移到Null Safety的案例研究

146 阅读9分钟

空值安全(Null Safety)通过在开发过程中而不是在运行时捕获空值错误,帮助我们避免Flutter应用程序中的整类错误。

随着Flutter 2的到来,我们可以将我们的项目迁移到Null Safety并充分利用它。

为了帮助我们,Dart文档中提供了一个方便的迁移指南。而且我们可以使用dart migrate 命令来自动完成部分迁移过程。

这在理论上听起来很不错,但对于有很多依赖关系的大型应用来说,这个过程可能会很漫长。

所以在这篇文章中,我将向你展示如何迁移一个用Flutter和Firebase构建的非简单的项目。

在我们讨论这个问题之前,让我们先把一件事弄清楚。

如何检查一个项目是否在使用Null Safety?

打开pubspec.yaml 文件,在environment 部分中检查Dart SDK的版本。

environment:
  sdk: ">=2.10.0 <3.0.0"
  • 你是否看到SDK:2.10.0或以下?那么你运行的是没有Null Safety的项目
  • 你看到sdk:2.12.0或以上?那么你运行的是Null Safety

请牢记这一点。

迁移到Null Safety是可选的。您可以选择将您的应用程序及其依赖关系升级到 Flutter 2**,而不选择 Null Safety**,方法是在您的pubspec.yaml 中保持 Dart SDK 为 2.10.0 或以下版本。

将Flutter和Firebase应用迁移至Null Safety

作为参考,我们将迁移我的时间跟踪应用程序,它是我的Flutter & Firebase课程的官方项目。

时间追踪应用程序截图

你可以在GitHub上找到这个项目,也可以在这个PR上找到已经完成的迁移。

作为参考,这个应用大约有2500行代码。

      45 text files.
      45 unique files.                              
       0 files ignored.

github.com/AlDanial/cloc v 1.84  T=0.03 s (1311.3 files/s, 82058.4 lines/s)
-------------------------------------------------------------------------------
Language                     files          blank        comment           code
-------------------------------------------------------------------------------
Dart                            45            265             13           2538
-------------------------------------------------------------------------------
SUM:                            45            265             13           2538
-------------------------------------------------------------------------------

为了使这一过程尽可能地无痛,我们将一步一步地完成迁移过程,并确保我们的应用程序在每一步之后都能正常工作

准备好了吗?让我们开始吧!

0.创建一个新的分支

由于我们要做大量的修改,最好在一个新的分支上工作。

git checkout -b null-safety-migration

这样一来,如果我们犯了什么错误,就可以回到工作状态了。

1.检查所有的依赖关系是否可以被升级

正如官方迁移指南中所述,最好先更新项目的所有依赖关系。

因此,让我们通过运行这个命令来检查哪些需要更新。

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:   
cloud_firestore         ✗0.14.30.14.31.0.01.0.0               
cupertino_icons         ✗1.0.01.0.21.0.21.0.2               
firebase_auth           ✗0.18.20.18.21.0.01.0.0               
firebase_core           ✗0.5.20.5.21.0.01.0.0               
flutter_login_facebook  ✗0.4.0+10.4.0+11.0.0-nullsafety.11.0.0-nullsafety.1  
google_sign_in          ✗4.5.64.5.65.0.05.0.0               
intl                    ✗0.16.10.16.10.17.00.17.0              
provider                ✗4.3.2+24.3.2+25.0.05.0.0               
rxdart                  ✗0.24.10.24.10.26.00.26.0              

dev_dependencies:      
mockito                 ✗4.1.34.1.35.0.05.0.0               

1 upgradable dependency is locked (in pubspec.lock) to an older version.
To update it, use `dart pub upgrade`.

9  dependencies are constrained to versions that are older than a resolvable version.
To update these dependencies, edit pubspec.yaml, or run `dart pub upgrade --null-safety`.

2.更新到所有最新的依赖项

上面的日志显示,所有的依赖关系都可以被升级。所以让我们运行这个。

dart pub upgrade --null-safety

这个过程完成后,pubspec.yaml ,用新的版本进行更新,我们得到这个输出。

  cupertino_icons: ^1.0.0 -> ^1.0.2
  firebase_core: 0.5.2 -> ^1.0.0
  firebase_auth: 0.18.2 -> ^1.0.0
  google_sign_in: 4.5.6 -> ^5.0.0
  flutter_login_facebook: 0.4.0+1 -> ^1.0.0-nullsafety.1
  provider: 4.3.2+2 -> ^5.0.0
  cloud_firestore: 0.14.3 -> ^1.0.0
  intl: 0.16.1 -> ^0.17.0
  rxdart: 0.24.1 -> ^0.26.0
  mockito: 4.1.3 -> ^5.0.0

3.安装更新后的依赖关系并处理破坏性变化

接下来,我们要安装所有的更新包。

flutter pub get

安装成功后,我们所有的依赖项现在都使用Null Safety。

但在我们将代码迁移到Null Safety之前,我们需要检查应用程序是否仍在编译和运行。

flutter analyze

事实证明,由于一些软件包的破坏性变化,我们有一些错误。

  error • There isn’t a setter named 'value' in class 'ValueStreamExtensions' at lib/app/sign_in/email_sign_in_bloc.dart:60:19 • (assignment_to_final_no_setter)
  error • Case expressions must be constant at lib/services/auth.dart:91:12 • (non_constant_case_expression)
  error • There's no constant named 'Success' in 'FacebookLoginStatus' at lib/services/auth.dart:91:32 • (undefined_enum_constant)
  error • Case expressions must be constant at lib/services/auth.dart:97:12 • (non_constant_case_expression)
  error • There's no constant named 'Cancel' in 'FacebookLoginStatus' at lib/services/auth.dart:97:32 • (undefined_enum_constant)
  error • Case expressions must be constant at lib/services/auth.dart:102:12 • (non_constant_case_expression)
  error • There's no constant named 'Error' in 'FacebookLoginStatus' at lib/services/auth.dart:102:32 • (undefined_enum_constant)

请记住,我们希望我们的应用程序在每一步都是可运行的。所以我们应该在继续前进之前修复这些错误。下面是一个提交,显示了这个阶段所有需要的修改。

这是一个很好的时机,可以将任何修改提交到我们在Git中的临时分支。

git add .
git commit -m "Upgrade dependencies + fixes for breaking changes"

4.修复iOS上的构建

接下来,我们可以试着运行我们的应用程序。安卓系统的构建似乎可以正常工作,但在iOS上,我们得到了一个很长的错误日志。实际的错误是。

[!] CocoaPods could not find compatible versions for pod "Firebase/Firestore":
  In snapshot (Podfile.lock):
    Firebase/Firestore (= 6.33.0, ~> 6.33.0)
  In Podfile:
    cloud_firestore (from `.symlinks/plugins/cloud_firestore/ios`) was resolved to 1.0.0, which depends on
      Firebase/Firestore (= 7.3.0)
You have either:
  * out-of-date source repos which you can update with `pod repo update` or with `pod install --repo-update`.
  * changed the constraints of dependency `Firebase/Firestore` inside your development pod `cloud_firestore`.
    You should run `pod update Firebase/Firestore` to apply changes you've made.

错误日志建议运行pod repo update 来解决这个问题。

Error: CocoaPods's specs repository is too out-of-date to satisfy dependencies.
To update the CocoaPods specs, run:
  pod repo update

Error running pod install
Error launching application on iPhone 12 Pro Max.

但在这种情况下,这不是解决方案。真正的问题是,cloud_firestore: ^1.0.0 需要iOS Firestore SDK的版本7.3.0 ,但目前安装的是版本6.33.0

在这种情况下,运行pod repo update 是不够的,因为我们的ios/Podfile.lock 文件仍然指向版本6.33.0 。让我们来解决这个问题。

cd ios
rm Podfile.lock
pod repo update

接下来,我们需要在我们的Xcode项目中设置iOS 10.0的最低部署目标

设置部署目标为10.0

而且我们还需要在Podfile的顶部设置这个。

platform :ios, '10.0'

这是新版本的iOS Firestore SDK的要求。

如果我们现在编译并运行iOS应用,一切都能正常工作。

Firestore是一个很大的库,有很多依赖关系,从头编译需要很长时间。关于如何加快构建速度,请看这个提示

这就完成了我们的准备工作。

  • 我们已经将所有的依赖关系迁移到Null Safe版本 ✅
  • 我们已经修复了代码中的破坏性变化 ✅
  • 我们的应用程序在iOS和Android上正确构建和运行 ✅

我们可以再次提交我们的修改,并开始进行迁移。

在尝试迁移到Null Safety之前,请确保你的应用程序能够在所有最新的软件包中正常运行。这将使你在以后的修改中更有信心。如果出了问题,你可以随时恢复到之前的提交。

5.运行迁移

让我们来运行这个。

dart migrate

输出结果会是这样的。

Migrating /Users/andrea/work/codewithandrea/github/flutter-course-apps/time_tracker_flutter_course

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

Analyzing project...
[--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------/\]

由于我们在上面做了所有的准备步骤,这个命令成功了,控制台打印出一个URL,我们可以打开这个URL来开始迁移过程。

View the migration suggestions by visiting:

  http://127.0.0.1:60278/Users/you/project/mypkg.console-simple?authToken=Xfz0jvpyeMI%3D

如果我们点击它,这个页面将在我们的浏览器中打开。

在这个阶段,我们可以。

  • 查看所有建议的修改
  • (可以选择)添加提示标记
  • 对所有的文件进行迁移(如果我们想分批进行,也可以选择一个子集)。

这个项目不是很大,所以我们可以将迁移应用到所有的文件,这将应用所有建议的修改。它还会在pubspec.yaml 文件中把Dart SDK的最低版本更新为2.12.0。

environment:
  sdk: ">=2.12.0 <3.0.0"

迁移工具会合理地进行正确的修改,但它并不完美。

在这个阶段,项目有可能无法编译。所以我们必须回到我们的代码中,审查所有的变化,并根据需要进行调整。这可以确保我们在这个阶段不会引入技术债务,并且我们有一个良好的基础来继续前进。

这可能是一个漫长的过程,你可能会发现一些有趣的问题。你可以查看这个提交,了解我所做的所有改动。

在这个阶段,这份文件是非常有用的。

Flutter团队还分享了这个有用的视频,详细解释了所有的步骤。

6.做一个烟雾测试

一旦我们对所有的变化感到满意,我们应该运行应用程序,并确保一切都能正常工作。

快速的烟雾测试是检查所有主要功能是否仍然工作的一个好方法。

7.移植所有的测试

除了lib 中的代码,这个项目还有一些单元和部件测试。这些也应该作为迁移的一部分被更新。

这些测试中有许多都依赖于mockito包。

不幸的是,mockito目前与Null Safety有一些严重的兼容性问题。目前,我们只能通过在构建中引入代码生成步骤来创建模拟类,这并不理想。

我已经按照这个NULL_SAFETY_README中的步骤让我的测试工作起来,但没有设法让它们都变成绿色。

Felix Angelov的mocktail包解决了这些问题,同时实现了与mockito非常相似的API,尽管我在等待,看这两个包的作者是否能达成一个适合所有人的解决方案。

因此,我暂时禁用了那些我无法工作的测试(YOLO 😅)。

总结

从开始到结束,这个项目的迁移过程花了不到一天时间。

按照正确的顺序执行所有的步骤绝对有帮助,我建议在你自己的项目中也这样做。

最大的痛点是要更新依赖mockito的测试。我希望mockito将来能支持Null Safety而不需要生成代码,因为很多项目都依赖于它。如果没有,我就转而使用mocktail

重申我在开始时所说的。

迁移到Null Safety是可选的。你可以选择将你的应用程序和它的依赖关系升级到Flutter 2,而不选择加入Null Safety

当你有机会的时候,我还是建议你进行迁移,这样你就可以用Sound Null Safety来运行你的应用程序和它们的所有依赖关系。

Dart文档涵盖了你需要知道的一切,你也可能发现我的Null Safety指南很有用。