Skip to content

Android plugins apply KGP based on AGP major version only, breaking on AGP 9 with android.builtInKotlin=false #3944

Description

@4akloon

Summary

Seven Android plugins decide whether to apply the Kotlin Gradle Plugin (KGP) based on the AGP major version alone:

val agpMajor = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.substringBefore('.').toInt()

if (agpMajor < 9) {
    apply(plugin = "org.jetbrains.kotlin.android")
}

This assumes AGP 9 always means built-in Kotlin is active. That is not true. AGP 9 enables built-in Kotlin only when android.builtInKotlin is not explicitly false — and android.builtInKotlin=false is exactly what flutter create writes into every new app's android/gradle.properties (via the Flutter migrator), and what every example app in this repository currently sets.

So on AGP 9 + android.builtInKotlin=false the guard is false, the plugin does not apply KGP, and AGP does not provide Kotlin either. Nothing compiles the plugin's Kotlin sources, and configuration fails a few lines below at the unconditional extension lookup:

* Where:
Build file '.../device_info_plus/android/build.gradle.kts' line: 35

* What went wrong:
Extension of type 'KotlinAndroidProjectExtension' does not exist. Currently registered extension types:
[ExtraPropertiesExtension, FlutterExtension, VersionCatalogsExtension, BasePluginExtension, SourceSetContainer,
ReportingExtension, JavaToolchainService, JavaPluginExtension, LibraryExtension, LibraryAndroidComponentsExtension,
LintLifecycleExtension, NamedDomainObjectContainer<BaseVariantOutput>]

Why this is not visible yet

Flutter's Gradle plugin applies kotlin-android on behalf of any Android subproject whose build script does not appear to declare KGP. It decides that by regex over the build file text, and the Kotlin-DSL regex (FlutterPluginUtils.kgpRegexKotlin) only matches plugins { } blocks:

internal val kgpRegexKotlin =
    """(?m)^[ \t]*plugins[ \t]*\{[^{}]*?(?<=[\n{])[ \t]*(?:id|alias)[ \t]*\(\s*(['"](?:kotlin-android|org\.jetbrains\.kotlin\.android)['"]|libs\.plugins\.(?:android|kotlin)\.android)\s*\)(?=[ \t]*(\n|${'$'}|\}))"""
        .toRegex()

It does not match the imperative apply(plugin = "…") form these plugins use. So Flutter does not see the declaration, applies KGP itself, and accidentally papers over the broken guard.

Note the asymmetry: the Groovy counterpart kgpRegexGroovy does match apply plugin: '…'. That is precisely why this bug has already been hit in a Groovy-based plugin but not here — see the prior art below.

This is load-bearing accident, not a contract. Flutter's own tooling already warns that this fallback is going away:

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.

Reproduction

Verified on AGP 9.1.0 / Kotlin 2.4.0 / Gradle 9.3.1, Flutter 3.44.8 stable, with android.builtInKotlin=false.

Because Flutter's fallback currently masks the bug, the decisive reproduction removes that mask. In a local Flutter SDK checkout, extend kgpRegexKotlin in packages/flutter_tools/gradle/src/main/kotlin/FlutterPluginUtils.kt so it also matches the imperative form, by prefixing this alternative to the existing pattern:

(?m)^[ \t]*apply[ \t]*\(\s*plugin[ \t]*=[ \t]*(['"])(?:kotlin-android|org\.jetbrains\.kotlin\.android)\1\s*\)|

Then, in any of the affected packages' examples, bump the example to AGP 9 and run flutter build apk --debug with android.builtInKotlin=false. The build fails with the KotlinAndroidProjectExtension does not exist error above. Reverting the SDK edit makes it build again — confirming the plugin currently depends on Flutter's fallback rather than on its own guard.

Evidence that the condition is wrong, not just unlucky

android.builtInKotlin defaults to enabled in AGP 9. From com/android/build/gradle/options/BooleanOption in gradle-9.1.0.jar:

3557: ldc_w   // String BUILT_IN_KOTLIN
3560: bipush  114
3562: ldc_w   // String android.builtInKotlin
3565: iconst_1                                  <-- defaultValue = true
3566: new     // class FeatureStage$SoftlyEnforced
3570: getstatic  // DeprecationTarget.VERSION_10_0

So: absent ⇒ built-in Kotlin on; explicit false ⇒ off. The version check alone cannot tell those apart. (It is also SoftlyEnforced with removal targeted at AGP 10, so opting out is temporary.)

Proposed fix

Guard on the same condition AGP itself uses — built-in Kotlin is on when AGP >= 9 and android.builtInKotlin is not explicitly false:

val agpMajor = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.substringBefore('.').toInt()

// AGP 9 provides Kotlin support natively unless the consuming app opts out with
// android.builtInKotlin=false, which is what `flutter create` writes by default.
val builtInKotlinEnabled =
    agpMajor >= 9 &&
        (providers.gradleProperty("android.builtInKotlin").orNull?.toBoolean() ?: true)

if (!builtInKotlinEnabled) {
    apply(plugin = "org.jetbrains.kotlin.android")
}

The project.extensions.configure(KotlinAndroidProjectExtension::class.java) { … } block below stays unchanged: AGP 9's built-in Kotlin registers that extension, so it resolves under both branches (confirmed on a real AGP 9.1 build with android.builtInKotlin=true).

On AGP 8 the behaviour is provably identical to today: agpMajor >= 9 is false, so builtInKotlinEnabled is false and KGP is applied exactly as before.

Note on prior art

app_settings 8.0.3 hit this exact failure and fixed it (spencerccf/app_settings#270); its android/build.gradle carries a long comment documenting the mechanism. Its fix is not worth copying verbatim: it reads the property with a ?: 'false' default and ignores the AGP version, so on an AGP 9 project that never sets the property it applies KGP into a build where built-in Kotlin is already active, which throws:

The 'org.jetbrains.kotlin.android' plugin is no longer required for Kotlin support since AGP 9.0.

Flutter apps always write the property, so that case is masked there — but the version above is correct in both cases.

Affected packages

  • android_alarm_manager_plus
  • battery_plus
  • device_info_plus
  • network_info_plus
  • package_info_plus
  • sensors_plus
  • share_plus

android_intent_plus uses a Groovy build.gradle with no Kotlin sources and is not affected.

Verification performed

Scenario Result
Patched Flutter regex, AGP 9, builtInKotlin=false, before fix Fails with KotlinAndroidProjectExtension does not exist
Patched Flutter regex, AGP 9, builtInKotlin=false, after fix APK built — all 7 packages
AGP 9, builtInKotlin=true APK built; KGP correctly not applied
Stock AGP 8.12.1 (current pin) APK built — all 7 packages

Additionally, after the fix, Flutter's warning lists the plugin under "plugins that apply KGP", which means the patched regex did detect the declaration and Flutter did not apply KGP as a fallback — the plugin stands on its own.

As a control, the fix was reverted in a single package (share_plus) and the AGP 9 build failed identically, confirming the passing results are meaningful rather than vacuous.

Per CONTRIBUTING, this is filed as one issue with a proposal, with one branch and pull request per package to follow.

Activity

  1. 4akloon commented on Jul 31, 2026

    @4akloon
    ContributorAuthor

    Pull requests, one per package as CONTRIBUTING requires:

    Each carries the identical one-file change and is independently mergeable; none of them closes this issue on merge, so it can be closed once all seven land (or repurposed if a different shape is preferred).

  2. sureshram25 commented on Aug 14, 2026

    @sureshram25

    Upgrade this alone we are facing the issues

    Dependency 'androidx.lifecycle:lifecycle-livedata-core-ktx:2.7.0' requires libraries and applications that
           depend on it to compile against version 34 or later of the
           Android APIs.
    
           :network_info_plus is currently compiled against android-33.
    
           Recommended action: Update this project to use a newer compileSdk
           of at least 34, for example 36.
    
           Note that updating a library or application's compileSdk (which
           allows newer APIs to be used) can be done separately from updating
           targetSdk (which opts the app in to new runtime behavior) and
           minSdk (which determines which devices the app can be installed
           on).
    
  3. dea6cat commented on Sep 8, 2026

    @dea6cat

    We hit this on AGP 9.0.1 with android.builtInKotlin=false and can add two
    symptoms that differ from the KotlinAndroidProjectExtension does not exist error in
    the report. On our setup configuration succeeds and the failure lands later, during
    compilation — so this may be worth listing as additional signatures of the same root
    cause.

    Environment: Flutter 3.47.2, AGP 9.0.1, Gradle 9.1.0, Kotlin 2.3.20, Gradle JDK 21,
    compileSdk/targetSdk 36, android.builtInKotlin=false, android.newDsl=false.

    Symptom A — plugin classes never reach the library's compile jar

    device_info_plus 13.2.0 and package_info_plus 10.2.1, flutter build apk --release:

    :app:compileDevReleaseJavaWithJavac
    GeneratedPluginRegistrant.java:39: error: cannot find symbol
          flutterEngine.getPlugins().add(new dev.fluttercommunity.plus.device_info.DeviceInfoPlusPlugin());
                                                                                  ^
      symbol:   class DeviceInfoPlusPlugin
      location: package dev.fluttercommunity.plus.device_info
    
    GeneratedPluginRegistrant.java:159: error: cannot find symbol
          flutterEngine.getPlugins().add(new dev.fluttercommunity.plus.packageinfo.PackageInfoPlugin());
                                                                                  ^
      symbol:   class PackageInfoPlugin
      location: package dev.fluttercommunity.plus.packageinfo
    2 errors
    

    The likely useful detail: the Kotlin compilation itself succeeds. Both of these
    pass when invoked directly, and the task does exist —

    ./gradlew :device_info_plus:tasks --all | grep compileReleaseKotlin   # present
    ./gradlew :device_info_plus:compileReleaseKotlin                      # rc=0
    ./gradlew :package_info_plus:compileReleaseKotlin                     # rc=0
    

    So classes are produced but never land in the module's compile_library_classes_jar,
    and consumers cannot resolve them.

    Symptom B — share_plus additionally loses its src/main/kotlin sources

    share_plus 13.3.0 does not compile at all:

    e: .../share_plus-13.3.0/android/src/main/kotlin/dev/fluttercommunity/plus/share/Share.kt:185:41
         Unresolved reference 'SharePlusPendingIntent'.
    e: .../Share.kt:185:71 Cannot infer type for type parameter 'T'. Specify it explicitly.
    

    SharePlusPendingIntent is declared in the same package, in
    android/src/main/kotlin/dev/fluttercommunity/plus/share/SharePlusPendingIntent.kt.
    build.gradle.kts has no sourceSets block registering src/main/kotlin, which KGP
    used to contribute implicitly — so alongside the property check, share_plus looks
    like it also needs that registration added back.

    Bisected: share_plus 13.0.0 builds fine (it still applies kotlin-android
    unconditionally); 13.3.0 fails as above.

    Two notes that may help narrow it down

    • The report lists sensors_plus as affected. On our setup (AGP 9.0.1)
      sensors_plus and flutter_email_sender_method_channel build fine, while
      device_info_plus, package_info_plus and share_plus fail — even though all five
      use the AGP-version-only check and all five configure
      KotlinAndroidProjectExtension. We could not work out what distinguishes them; given
      the report used AGP 9.1.0, an AGP patch-version difference may be involved.
    • These three are awkward to test in isolation because win32 ^6 couples them:
      package_info_plus ≥10.1.0 requires win32 ^6.0.1, which forces share_plus
      ≥13.0.0 and device_info_plus ≥13.2.0. We could only observe them together, so
      please verify each package separately rather than trusting our grouping.

    This also blocks upgrading file_picker to 12.x, since that upgrade pulls the same
    win32 ^6 cluster.

  4. gabrielgarciagava commented on Sep 16, 2026

    @gabrielgarciagava

    In case it is not clear from the Summary of ticket, what this causes is that if we have one of these packages as a dependency, when we try to migrate to AGP >= 9, we need to be sure that all dependencies also support built-in kotlin, otherwise the project will not build for android, and this makes it very hard to move to AGP 9 (which makes it important since flutter will eventually drop support to AGP < 9)

  5. mahendrimd commented on Sep 21, 2026

    @mahendrimd
    Contributor

    I just had time to rebuild one of my apps, and I can't reproduce the issue described here.

    My environment is:

    • Flutter 3.47
    • AGP 9.2.1
    • Gradle 9.5

    with the following gradle.properties

    # other settings
    ...
    # This newDsl flag was added automatically by Flutter migrator
    android.newDsl=false
    # This builtInKotlin flag was added automatically by Flutter migrator
    android.builtInKotlin=false
    

    I'm using share_plus and package_info_plus, and the app bundle builds successfully with the following warnings:

    Running Gradle task 'bundleRelease'...
    WARNING: Your app uses the following plugins that apply Kotlin Gradle Plugin (KGP): firebase_analytics, firebase_app_check, firebase_core, firebase_crashlytics
    Future versions of Flutter will fail to build if your app uses plugins that apply KGP.
    

    Would it be possible to reproduce the issue using one of the demo/example apps? That might make it easier for the maintainers to reproduce and confirm the problem.

  6. gabrielgarciagava commented on Sep 21, 2026

    @gabrielgarciagava

    Just to be sure, did you set android.builtInKotlin=false?

  7. mahendrimd commented on Sep 22, 2026

    @mahendrimd
    Contributor

    Right, I just added my gradle.properties above.

    I didn't do much before running flutter build appbundle, other than:

    1. upgrading Flutter from 3.44 to 3.47, and
    2. upgrading all dependencies.
  8. CleanClubManager commented on Sep 24, 2026

    @CleanClubManager

    Cross-reference: I left a longer report on #3949 (the package_info_plus patch), rather
    than duplicate it here — #3949 (comment)

    The short version, on Flutter 3.47.0 / AGP 9.0.1 / Gradle 9.1.0: the workaround usually
    suggested for this issue, android.builtInKotlin=true, is not available on that Flutter
    version. AGP's built-in Kotlin pins KGP 2.2.10 (same pin in the AGP POMs from 9.0.1
    through 9.4.1) and Flutter refuses to build below 2.2.20, so both positions of the
    switch fail. Downgrading package_info_plus to 9.0.1 does not help either, because
    file_picker ≥ 12 — the first version that handles AGP 9 correctly — requires
    package_info_plus: ^10.2.1.

    Also worth noting for whoever picks this up: Flutter's own KGP fallback
    (FlutterPluginUtils.detectApplyingKotlinGradlePlugin) does not cover these modules. It
    decides by looking for the plugin declaration in the build file, and the
    apply(plugin = "org.jetbrains.kotlin.android") is present — inside the dead
    if (agpMajor < 9) branch — so it concludes KGP is already applied and skips the module.

    The seven PRs from 2026-07-31 are all still open and unreviewed.

  9. moritz-weber commented on Oct 4, 2026

    @moritz-weber

    I had the same cannot find symbol error. Switching from Gradle 9.4 to 9.5 solved the issue for me. Using Flutter 3.44 and AGP 9.2.1.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions