Flutter 3.x 版本升级实战:让老项目焕发新生

随着 Flutter 框架的不断更新,开发者时常需要面对项目升级和改造的问题。从 Flutter 2.xFlutter 3.x 的版本不仅带来了新特性,也引入了许多 breaking changes 和架构优化。最近在做的就是 Flutter 老项目的升级改造,将 Flutter 的版本升到最新的stable 3.27.1,这里记录一下更新过程中遇到的问题及解决办法,也希望对你有所帮助。

null safety 支持

支持 null safety 可以帮助你在编译时捕捉空值相关的错误,避免运行时错误和异常,也是本次老项目升级改造改动代码最多的。通常的做法就是,如果变量不允许为 null,则使用具体的 Type 类型,而不是 Type?(即声明为非可空类型),如 String name = 'John';

对于某个可以为 null 的类型,使用类型后加 ? 来表示它是可空类型。例如,String? 表示该值可以是一个字符串或 nullString? 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 []});
}

但是这样给数组赋默认值有一个问题,就是在后面使用的过程中,数组devicesImmutable类型的,不能向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**)有详细的解决步骤,这里简单介绍一下我在项目中的操作。

  1. 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 库。

  1. AndroidManifest.xmlapplication 标签中添加 android:name
xml 复制代码
<application 
        android:name="androidx.multidex.MultiDexApplication" >
        ...
</application>
  1. 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)
    }
}
  1. 如果前面三步配置好之后运行没问题的话,可以忽略该步骤。如果还是不行,老规矩尝试 Build -> Clean Project ,然后 File -> Invalidate Caches / Restart

小结

除了上面记录的问题,还有 Flutter 3.0 及第三方的依赖库引入了一些 API 的更改或弃用。在迁移时,可能需要对一些弃用的 API 进行替换,或者更新你的依赖库以确保兼容性。改造完成后还需要对整个 App 的功能进行测试,以确保能够正常运行。

相关推荐
betazhou2 分钟前
mariadb5.5.56在centos7.6环境安装
android·数据库·adb·mariadb·msyql
EndingCoder3 小时前
React从基础入门到高级实战:React 实战项目 - 项目三:实时聊天应用
前端·react.js·架构·前端框架
doublelixin6 小时前
AOSP (Android11) 集成Google GMS三件套
android
后海 0_o8 小时前
2025前端微服务 - 无界 的实战应用
前端·微服务·架构
喵叔哟8 小时前
24.【.NET8 实战--孢子记账--从单体到微服务--转向微服务】--单体转微服务--认证微服务
微服务·架构·.net
xzkyd outpaper8 小时前
onSaveInstanceState() 和 ViewModel 在数据保存能力差异
android·计算机八股
java干货9 小时前
虚拟线程与消息队列:Spring Boot 3.5 中异步架构的演进与选择
spring boot·后端·架构
SoFlu软件机器人9 小时前
智能生成完整 Java 后端架构,告别手动编写 ControllerServiceDao
java·开发语言·架构
西陵9 小时前
前端框架渲染DOM的的方式你知道多少?
前端·javascript·架构
CYRUS STUDIO10 小时前
FART 脱壳某大厂 App + CodeItem 修复 dex + 反编译还原源码
android·安全·逆向·app加固·fart·脱壳