Keep Flutter Plugins Alive on Old and New AGP Without Breaking Changes
Welcome to follow the WeChat official account: FSA Full Stack Action 👋
I. Overview
Friends who have recently upgraded to Flutter 3.44.x and above have probably been spammed by this prominent warning in the console when compiling projects:
WARNING: Your app uses the following plugins that apply Kotlin Gradle Plugin (KGP)... Future versions of Flutter will fail to build if your app uses plugins that apply KGP.
The dreaded KGP (Kotlin Gradle Plugin) is about to be "abandoned"!
It turns out that starting from Android Gradle Plugin (AGP) 9.0, Kotlin support has been completely incorporated as "Built-in Kotlin." Any plugin that unilaterally declares apply plugin: 'kotlin-android' will directly cause a build crash in future Flutter compilation environments.
The official guide (Built-in Kotlin migration for plugin authors) provides a very direct path: just raise the plugin's minimum Flutter version limit to 3.44 in one go, then delete apply plugin: 'kotlin-android', and you're done!
However, in the maintenance of actual open-source components, if we as maintainers forcibly raise the minimum version to Flutter 3.44, it's equivalent to "burning bridges" with users who are still sticking to early versions of Flutter 3.x (or even Flutter 3.0)! Therefore, this guide will take you through a set of adaptation secrets for bidirectional compatibility with old and new Flutter versions.
II. Comparison of Old and New Versions
Before we start, let's use a table to intuitively compare the mechanism differences between the old and new compilation environments. Know yourself and know your enemy, and you can "prescribe the right medicine":
| Dimension | Old Flutter Build System (< 3.44) | New Flutter Build System (>= 3.44 / AGP 9+) |
|---|---|---|
| Kotlin Loading Mechanism | Relies on plugins explicitly activating via kotlin-android |
Handled internally by AGP 9.0+; manual apply by plugins conflicts with built-in logic |
| Kotlin Plugin Configuration | Must introduce KGP dependency in buildscript.dependencies |
Handed over to the main App project or managed by the Flutter build chain; no need to re-declare in sub-plugins |
| JVM Target Setting | Must be specified inside android via kotlinOptions {} |
Introduces the new outer-level kotlin { compilerOptions {} } DSL |
III. Dynamic Compatibility Solution
Here, taking the chat_bottom_container plugin we maintain as an example, to allow the plugin to compile normally in old Flutter (AGP < 9.0) environments, while not causing KGP conflicts under the new Built-in Kotlin (AGP >= 9.0), we need to adopt a "dynamic sniffing" strategy in chat_bottom_container/android/build.gradle.
1. Dynamically Apply the kotlin-android Plugin
We decide whether to manually apply the plugin by parsing the actual AGP version number and checking if the host project has enabled Built-in Kotlin:
// 1. Get the major version number of the Android Gradle plugin in the current build environment
def agpMajor = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize(".")[0] as int
// 2. Determine if Built-in Kotlin is currently active (requires AGP version >= 9 and not explicitly disabled)
def builtInKotlinActive = agpMajor >= 9 &&
(!project.hasProperty("android.builtInKotlin") ||
Boolean.parseBoolean(project.property("android.builtInKotlin").toString()))
// 3. Only manually apply the old kotlin-android plugin in old build environments where Built-in Kotlin is not active
if (!builtInKotlinActive) {
apply plugin: "kotlin-" + "android"
}
- During the Gradle configuration phase, get the current AGP major version number (e.g., 8 or 9) from
com.android.Version. - Combine
agpMajorand theandroid.builtInKotlinproperty to calculate thebuiltInKotlinActiveboolean value, used to determine if Built-in Kotlin is currently active. - If the host project still hasn't enabled Built-in Kotlin, we apply a "patch" by manually executing
apply pluginvia dynamic string concatenation, maintaining backward compatibility while avoiding misjudgment by Flutter.
Q: Why not directly use
apply plugin: "kotlin-android"?A: This is to avoid false positives caused by the limitations of Flutter CLI's static regex scanning mechanism.
When building an application, the Flutter build tool (
flutter_tools) does not actually parse and execute Groovy'sif-elseconditional branches. It simply and crudely uses static regular expressions (RegExp) to scan thebuild.gradletext of dependent plugins. As long as the literalapply plugin: "kotlin-android"orid "org.jetbrains.kotlin.android"appears in the file, regardless of whether this code is inside a disabled branch, it will be judged as "manually applied KGP," thus throwing a warning.
2. Dynamically Configure JVM Target Version (jvmTarget)
Due to different Kotlin plugin versions, there are two sets of DSL syntax for setting the JVM target. If you directly write the new syntax, old projects will fail to compile under old KGP versions (property not found); write the old syntax, and it will throw deprecation or unsupported errors when compiling in the new environment.
Therefore, we need to adopt the following dynamic property sniffing method:
// 1. Get the kotlin extension configuration object from the project
def kotlinExt = project.extensions.findByName("kotlin")
// 2. Check if this extension object supports the compilerOptions property
if (kotlinExt?.hasProperty("compilerOptions")) {
kotlin {
compilerOptions {
jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_1_8
}
}
} else {
// 3. Fallback to using the traditional kotlinOptions configuration method to prevent errors in old Kotlin Gradle plugins
android.kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
}
- Safely get the Kotlin extension object via
extensions.findByName("kotlin")to prevent null pointer exceptions caused by the project not having Kotlin introduced. - If this extension contains the new
compilerOptions, it indicates the current environment is new KGP (1.9+) or Built-in Kotlin. In this case, directly use the newcompilerOptionssyntax to configure the JVM target. - If it's an old version, fallback to using the traditional
android.kotlinOptions { jvmTarget = '1.8' }syntax to ensure smooth compilation of old projects.
IV. Conclusion
Through the above two Gradle dynamic determinations and clever literal concatenation, the plugin can smoothly be compatible with both old and new versions of Flutter and Kotlin compilation environments without making any Breaking Changes.
For users, simply introduce the modified plugin directly; there is no need to forcibly add palliative avoidance configurations like android.builtInKotlin=false in the main project's gradle.properties.
I hope this secret manual helps you friends step on fewer compilation pitfalls. See you next time!
If the article was helpful to you, please don't hesitate to click and follow my WeChat official account: FSA Full Stack Action, which will be the greatest encouragement to me. The official account has not only Android technology, but also articles on iOS, Python, etc., possibly covering skill points you might want to know~