随着 Flutter
框架的不断更新,开发者时常需要面对项目升级和改造的问题。从 Flutter 2.x
到 Flutter 3.x
的版本不仅带来了新特性,也引入了许多 breaking changes
和架构优化。最近在做的就是 Flutter
老项目的升级改造,将 Flutter
的版本升到最新的stable 3.27.1
,这里记录一下更新过程中遇到的问题及解决办法,也希望对你有所帮助。
null safety
支持
支持 null safety
可以帮助你在编译时捕捉空值相关的错误,避免运行时错误和异常,也是本次老项目升级改造改动代码最多的。通常的做法就是,如果变量不允许为 null
,则使用具体的 Type
类型,而不是 Type?
(即声明为非可空类型),如 String name = 'John';
。
对于某个可以为 null
的类型,使用类型后加 ?
来表示它是可空类型。例如,String?
表示该值可以是一个字符串或 null
,String? name = null;
。
对于不可为 null
的变量,但又要确保它们在构造函数中被正确初始化。可以使用 late
关键字来延迟初始化某个变量。
dart
late String name;
name = 'John';
在使用可空类型的变量时,需要进行 null
检查。例如,使用 ?.
和 ??
来避免空引用错误。
dart
String? name;
print(name?.length); // 如果 name 为 null,则不会引发异常
// 使用默认值
String displayName = name ?? 'Guest';
项目中的代码改造成支持 null safety
之后,还需要更新 pubspec.yaml
文件中依赖的版本,确保它们与 null safety
兼容。我这里使用 flutter pub outdated
命令查看需要更新的插件版本,然后手动编辑更新,一旦发现报错及时修改。当然也可以用以下命令一起更新:
Console
flutter pub upgrade --major-versions
构造函数中如何给数组类型的变量赋默认值,通常的做法是这样的。
dart
class GatewayModel {
List<DeviceModel> devices;
// 如果没有提供参数,则使用默认空数组
GatewayModel({this.devices = const []});
}
但是这样给数组赋默认值有一个问题,就是在后面使用的过程中,数组devices
是Immutable
类型的,不能向devices
数组内添加元素。可以在this.devices
前面加上required
,也就是说调用构造函数时必须给devices
赋值,或者像下面这样初始化devices
。
dart
class GatewayModel {
List<DeviceModel> devices;
// GatewayModel({required this.devices});
GatewayModel({List<DeviceModel>? devices}): this.devices = devices ?? [];
}
当然以上做法对于 map
类型也同样适用。
Namespace not specified
报错问题
Flutter
更新到3.27.1
后,相应的需要升级 Android Gradle Plugin
(AGP
) 至兼容版本,当 android
工程的 AGP
大于等于 8.x.x
以上时,该报错会出现。
Console
FAILURE: Build failed with an exception.
* What went wrong:
A problem occurred configuring project ':app'.
> Could not create an instance of type com.android.build.api.variant.impl.ApplicationVariantImpl.
> Namespace not specified. Specify a namespace in the module's build file: xxx/android/app/build.gradle. See https://d.android.com/r/tools/upgrade-assistant/set-namespace for information about setting the namespace.
If you've specified the package attribute in the source AndroidManifest.xml, you can use the AGP Upgrade Assistant to migrate to the namespace value in the build file. Refer to https://d.android.com/r/tools/upgrade-assistant/agp-upgrade-assistant for general information about using the AGP Upgrade Assistant.
根据上述报错给出的提示,首先使用 AGP Upgrade Assistant
来更新 gradle
版本。打开android
工程后,在菜单栏中找到并点击Tool ->AGP Upgrade Assistant ,更新完成后,在android->build.gradle
的文件中添加如下配置。
gradle
allprojects {
repositories {
google()
mavenCentral()
}
// add this code
subprojects {
afterEvaluate { project ->
if (project.hasProperty('android')) {
project.android {
if (namespace == null) {
namespace project.group
}
}
}
}
}
// add this code
}
添加这个配置的目的是给让当前 Flutter
项目中所有的子项目或插件都加上 namespace
,以便适配最新的 gradle
版本。再次运行发现还会报错:
Console
Caused by: java.lang.ClassNotFoundException: Didn't find class "android.MainActivity" on path: DexPathList[[zip file
那就在android
->app
->build.gradle
文件中配置一下namespace
:
gradle
android {
...
namespace "应用的包名"
...
}
上述操作完成后,如果还是有报错,尝试 Build -> Clean Project ,然后 File -> Invalidate Caches / Restart。
MultiDex
报错问题
这也是一个比较坑的问题,因为在 Flutter
运行在 Android
手机设备上的时候一直卡在 Installing apk
这里,也没有看到任何报错,打开 android
项目运行知道是 MultiDex
未启用。
好在解决起来也不难,Android 的官方文档中(**https://developer.android.com/build/multidex?hl=zh-cn#kotlin**)有详细的解决步骤,这里简单介绍一下我在项目中的操作。
- 在
android
->app
->build.gradle
文件中启用MultiDex
,并将MultiDex
库添加为依赖项。
gradle
android {
defaultConfig {
minSdk = 24
multiDexEnabled = true // 1. open MultiDex enable
...
}
}
dependencies {
implementation 'androidx.multidex:multidex:2.0.1' // 2. add it as a dependency here
}
位置1打开multiDexEnabled
,位置2设置依赖项。这里需要说明一下,如果 minSdkVersion
为21或更高版本,会默认启用 MultiDex
,并且您不需要 手动配置 MultiDex
库,反之,如果则需要。我这里的 minSdk
虽然是24,但是项目中其它组件用到的 minSdk
为16,所以还是需要收到配置 MultiDex
库。
- 在
AndroidManifest.xml
的application
标签中添加android:name
xml
<application
android:name="androidx.multidex.MultiDexApplication" >
...
</application>
- 在
MainActivity
中重写attachBaseContext
方法,并调用MultiDex.install
。
kotlin
import android.content.Context
import androidx.multidex.MultiDex
class MainActivity: FlutterActivity(){
override fun attachBaseContext(base: Context) {
super.attachBaseContext(base)
MultiDex.install(this)
}
}
- 如果前面三步配置好之后运行没问题的话,可以忽略该步骤。如果还是不行,老规矩尝试 Build -> Clean Project ,然后 File -> Invalidate Caches / Restart。
小结
除了上面记录的问题,还有 Flutter 3.0
及第三方的依赖库引入了一些 API
的更改或弃用。在迁移时,可能需要对一些弃用的 API
进行替换,或者更新你的依赖库以确保兼容性。改造完成后还需要对整个 App
的功能进行测试,以确保能够正常运行。