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 的功能进行测试,以确保能够正常运行。

相关推荐
Yang-Never43 分钟前
Kotlin->Kotlin协程的取消机制
android·java·开发语言·kotlin·android studio·idea
言之。3 小时前
【架构设计】新闻推送系统设计
架构
嶂蘅4 小时前
【调研】Android app动态更新launcher_icon
android·前端·程序员
woodWu4 小时前
Android 稳定性(二):治理思路篇
android
阿豪元代码4 小时前
彻底掌握 Android14 Vsync 原理
android
parade岁月4 小时前
uniapp Android 原生插件开发-Module扩展为例-从开发到测试到部署到uniapp项目
android·uni-app
molong9314 小时前
Android中Activity
android
Sinyu10124 小时前
Flutter 动画实战:绘制波浪动效详解
android·前端·flutter
小小の灰色脑细胞4 小时前
【Flutter】使用ScrollController配合EasyRefresh实现列表预加载:在还未滑动到底部时加载下一页数据
flutter
sunly_4 小时前
Flutter:文章详情页,渲染富文本
android·javascript·flutter