Skip to content

Edit the 3.35 blog to warn users that abiFilters is now set in Flutter apps #174004

Closed
flutter/website
#12350

Description

@angcyo

Steps to reproduce

android {
    ...
    defaultConfig {
        ...

        ndk {
            abiFilters.add("arm64-v8a")
        }
    }
}

Expected results

The abi should only include arm64_v8a.

Actual results

The output abi will still include "x86_64", "arm64_v8a" and "armeabi-v7a".

Image

Code sample

Sample source code project:
https://github.com/angcyo/flutter_abi_filters_demo

Flutter Doctor output

Doctor output
[✓] Flutter (Channel stable, 3.35.1, on macOS 15.6 24G84 darwin-arm64, locale zh-Hans-CN) [310ms]
    • Flutter version 3.35.1 on channel stable at /Users/angcyo/Library/Android/flutter
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision 20f8274939 (4 天前), 2025-08-14 10:53:09 -0700
    • Engine revision 1e9a811bf8
    • Dart version 3.9.0
    • DevTools version 2.48.0
    • Feature flags: enable-web, enable-linux-desktop, enable-macos-desktop, enable-windows-desktop, enable-android, enable-ios, cli-animations,
      enable-lldb-debugging

[!] Android toolchain - develop for Android devices (Android SDK version 36.0.0) [1,120ms]
    • Android SDK at /Users/angcyo/Library/Android/sdk
    • Emulator version 36.2.4.0 (build_id 13899551) (CL:N/A)
    • Platform android-36, build-tools 36.0.0
    • Java binary at: /Applications/Android Studio Preview.app/Contents/jbr/Contents/Home/bin/java
      This is the JDK bundled with the latest Android Studio installation on this machine.
      To manually set the JDK path, use: `flutter config --jdk-dir="path/to/jdk"`.
    • Java version OpenJDK Runtime Environment (build 21.0.7+-13880790-b1038.58)
    ! Some Android licenses not accepted. To resolve this, run: flutter doctor --android-licenses

[✓] Xcode - develop for iOS and macOS (Xcode 16.4) [699ms]
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Build 16F6
    • CocoaPods version 1.16.2

[✓] Chrome - develop for the web [80ms]
    • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[✓] Android Studio (version 2025.1) [80ms]
    • Android Studio at /Applications/Android Studio Beta.app/Contents
    • Flutter plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/6351-dart
    • Java version OpenJDK Runtime Environment (build 21.0.7+-13880790-b1038.58)

[✓] Android Studio (version 2025.1.4) [79ms]
    • Android Studio at /Applications/Android Studio Preview.app/Contents
    • Flutter plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/6351-dart
    • Java version OpenJDK Runtime Environment (build 21.0.7+-13880790-b1038.58)

[✓] Android Studio (version 2024.3) [79ms]
    • Android Studio at /Applications/Android Studio.app/Contents
    • Flutter plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/6351-dart
    • Java version OpenJDK Runtime Environment (build 21.0.6+-13355223-b631.42)

[✓] Android Studio (version 2025.1) [79ms]
    • Android Studio at /Users/angcyo/Applications/Android Studio.app/Contents
    • Flutter plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/6351-dart
    • Java version OpenJDK Runtime Environment (build 21.0.6+-13391695-b895.109)

[✓] IntelliJ IDEA Ultimate Edition (version 2025.2) [79ms]
    • IntelliJ at /Users/angcyo/Applications/IntelliJ IDEA Ultimate.app
    • Flutter plugin version 86.0.2
    • Dart plugin version 252.24322.5

[✓] VS Code (version 1.103.1) [7ms]
    • VS Code at /Applications/Visual Studio Code.app/Contents
    • Flutter extension version 3.116.0

[✓] Connected device (3 available) [5.8s]
    • Pixel 6 (mobile) • 1C151FDF60063X • android-arm64  • Android 16 (API 36)
    • macOS (desktop)  • macos          • darwin-arm64   • macOS 15.6 24G84 darwin-arm64
    • Chrome (web)     • chrome         • web-javascript • Google Chrome 139.0.7258.128
    ! Error: Browsing on the local area network for angcyoo. Ensure the device is unlocked and attached with a cable or associated with the same local area
      network as this Mac.
      The device must be opted into Developer Mode to connect wirelessly. (code -27)

[✓] Network resources [1,133ms]
    • All expected network resources are available.

! Doctor found issues in 1 category.

Activity

  1. angcyo commented on Aug 19, 2025

    @angcyo
    Author

    I saw it in the FlutterPlugin.kt file:

            // By default, assembling APKs generates fat APKs if multiple platforms are passed.
            // Configuring split per ABI allows to generate separate APKs for each abi.
            // This is a noop when building a bundle.
            if (FlutterPluginUtils.shouldProjectSplitPerAbi(project)) {
                FlutterPluginUtils.getAndroidExtension(project).splits.abi {
                    isEnable = true
                    reset()
                    isUniversalApk = false
                }
            } else {
                // When splits-per-abi is NOT enabled, configure abiFilters to control which
                // native libraries are included in the APK.
                //
                // This is crucial: If a project includes third-party dependencies with x86 native libraries,
                // without these abiFilters, Google Play would incorrectly identify the app as supporting x86.
                // When users with x86 devices install the app, it would crash at runtime because Flutter's
                // native libraries aren't available for x86. By filtering out x86 at build time, Google Play
                // correctly excludes x86 devices from the compatible device list.
                //
                // NOTE: This code does NOT affect "add-to-app" scenarios because:
                // 1. For 'flutter build aar': abiFilters have no effect since libflutter.so and libapp.so
                //    are not packaged into AAR artifacts - they are only added as dependencies
                //    in pom files.
                // 2. For project dependencies (implementation(project(":flutter"))): The Flutter
                //    Gradle Plugin is not applied to the main app subproject, so this apply()
                //    method is never called.
                //
                // abiFilters cannot be added to templates because it would break builds when
                // --splits-per-abi is used due to conflicting configuration. This approach
                // adds them programmatically only when splits are not configured.
                //
                // If the user has specified abiFilters in their build.gradle file, those
                // settings will take precedence over these defaults.
                FlutterPluginUtils.getAndroidExtension(project).buildTypes.forEach { buildType ->
                    buildType.ndk.abiFilters.clear()
                    FlutterPluginConstants.DEFAULT_PLATFORMS.forEach { platform ->
                        val abiValue: String =
                            FlutterPluginConstants.PLATFORM_ARCH_MAP[platform]
                                ?: throw GradleException("Invalid platform: $platform")
                        buildType.ndk.abiFilters.add(abiValue)
                    }
                }
            }
    object FlutterPluginConstants {
        /** The platforms that can be passed to the `--Ptarget-platform` flag. */
        private const val PLATFORM_ARM32 = "android-arm"
        private const val PLATFORM_ARM64 = "android-arm64"
        private const val PLATFORM_X86_64 = "android-x64"
    
        ...
    
        /** When split is enabled, multiple APKs are generated per each ABI. */
        @JvmStatic val DEFAULT_PLATFORMS =
            listOf(
                PLATFORM_ARM32,
                PLATFORM_ARM64,
                PLATFORM_X86_64
            )
    }

    amazed...

  2. angcyo commented on Aug 19, 2025

    @angcyo
    Author

    I found the submission record of this piece of code:
    1c8ce95

    temporary solution, modify the source code of the plugin directly:

        @JvmStatic val DEFAULT_PLATFORMS =
            listOf(
                PLATFORM_ARM32,
                PLATFORM_ARM64,
            )
    

    PR:
    #174008

  3. darshankawar commented on Aug 19, 2025

    @darshankawar
    Member

    Thanks for the detailed report and analysis. I was able to replicate this on latest stable and master versions.

    stable : 3.35.1
    master : 3.33.0-1.0.pre-1457
    
  4. added
    platform-androidAndroid applications specifically
    has reproducible stepsThe issue has been confirmed reproducible and is ready to work on
    team-androidOwned by Android platform team
    and removed
    in triagePresently being triaged by the triage team
    on Aug 19, 2025
  5. RaadShariat commented on Aug 19, 2025

    @RaadShariat
  6. added
    P2Important issues not at the top of the work list
    triaged-androidTriaged by Android platform team
    on Aug 19, 2025
  7. 8 remaining items

  8. komaxx commented on Aug 20, 2025

    @komaxx

    Sorry, I'm confused what the solution is here.
    We used to limit abis by just adding a line in gradle

    ndk.abiFilters "armeabi-v7a", "arm64-v8a"
    

    Now, this is no longer working. Is there a new way to achieve the same without modifying the source code of the plugin?

  9. angcyo commented on Aug 20, 2025

    @angcyo
    Author

    Sorry, I'm confused what the solution is here. We used to limit abis by just adding a line in gradle

    ndk.abiFilters "armeabi-v7a", "arm64-v8a"
    

    Now, this is no longer working. Is there a new way to achieve the same without modifying the source code of the plugin?

    Waiting for the new version of flutter.

    or modify the source code of the flutter sdk FlutterPlugin.kt file in {flutter_sdk_path}/packages/flutter_tools/gradle/src/main/kotlin/FlutterPlugin.kt.

    Copy the following code to replace this file:
    // Copyright 2014 The Flutter Authors. All rights reserved.
    // Use of this source code is governed by a BSD-style license that can be
    // found in the LICENSE file.
    
    package com.flutter.gradle
    
    import com.android.build.api.dsl.ApplicationExtension
    import com.android.build.gradle.AbstractAppExtension
    import com.android.build.gradle.BaseExtension
    import com.android.build.gradle.LibraryExtension
    import com.android.build.gradle.tasks.PackageAndroidArtifact
    import com.android.build.gradle.tasks.ProcessAndroidResources
    import com.flutter.gradle.FlutterPluginUtils.readPropertiesIfExist
    import com.flutter.gradle.plugins.PluginHandler
    import com.flutter.gradle.tasks.FlutterTask
    import org.gradle.api.GradleException
    import org.gradle.api.Plugin
    import org.gradle.api.Project
    import org.gradle.api.Task
    import org.gradle.api.UnknownTaskException
    import org.gradle.api.file.Directory
    import org.gradle.api.tasks.Copy
    import org.gradle.api.tasks.TaskInstantiationException
    import org.gradle.api.tasks.TaskProvider
    import org.gradle.api.tasks.bundling.Jar
    import org.gradle.internal.os.OperatingSystem
    import org.gradle.kotlin.dsl.support.serviceOf
    import org.gradle.process.ExecOperations
    import java.io.File
    import java.nio.charset.StandardCharsets
    import java.nio.file.Paths
    import java.util.Properties
    
    class FlutterPlugin : Plugin<Project> {
        private var project: Project? = null
        private var flutterRoot: File? = null
        private var flutterExecutable: File? = null
        private var localEngine: String? = null
        private var localEngineHost: String? = null
        private var localEngineSrcPath: String? = null
        private var localProperties: Properties? = null
        private var engineVersion: String? = null
        private var engineRealm: String? = null
        private var pluginHandler: PluginHandler? = null
    
        override fun apply(project: Project) {
            this.project = project
    
            val rootProject = project.rootProject
            if (FlutterPluginUtils.isFlutterAppProject(project)) {
                addTaskForLockfileGeneration(rootProject)
            }
    
            val flutterRootSystemVal: String? = System.getenv("FLUTTER_ROOT")
            val flutterRootPath: String =
                resolveFlutterSdkProperty(flutterRootSystemVal)
                    ?: throw GradleException(
                        "Flutter SDK not found. Define location with flutter.sdk in the " +
                            "local.properties file or with a FLUTTER_ROOT environment variable."
                    )
    
            flutterRoot = project.file(flutterRootPath)
            if (!flutterRoot!!.isDirectory) {
                throw GradleException("flutter.sdk must point to the Flutter SDK directory")
            }
    
            engineVersion =
                if (FlutterPluginUtils.shouldProjectUseLocalEngine(project)) {
                    "+" // Match any version since there's only one.
                } else {
                    val engineStampPath =
                        Paths.get(flutterRoot!!.absolutePath, "bin", "cache", "engine.stamp")
                    val engineStampContent = engineStampPath.toFile().readText().trim()
                    "1.0.0-$engineStampContent"
                }
    
            engineRealm =
                Paths
                    .get(flutterRoot!!.absolutePath, "bin", "cache", "engine.realm")
                    .toFile()
                    .readText()
                    .trim()
            if (engineRealm!!.isNotEmpty()) {
                engineRealm += "/"
            }
    
            // Configure the Maven repository.
            val hostedRepository: String =
                System.getenv(FlutterPluginConstants.FLUTTER_STORAGE_BASE_URL)
                    ?: FlutterPluginConstants.DEFAULT_MAVEN_HOST
            val repository: String? =
                if (FlutterPluginUtils.shouldProjectUseLocalEngine(project)) {
                    project.property(PROP_LOCAL_ENGINE_REPO) as String?
                } else {
                    "$hostedRepository/${engineRealm}download.flutter.io"
                }
            rootProject.allprojects {
                repositories.maven {
                    url = uri(repository!!)
                }
                if (plugins.hasPlugin("com.android.application") && isInvokedFromAndroidStudio()) {
                    dependencies.add("compileOnly", "io.flutter:flutter_embedding_debug:$engineVersion")
                    dependencies.add("compileOnly", "io.flutter:armeabi_v7a_debug:$engineVersion")
                    dependencies.add("compileOnly", "io.flutter:arm64_v8a_debug:$engineVersion")
                    dependencies.add("compileOnly", "io.flutter:x86_64_debug:$engineVersion")
                }
            }
    
            project.apply {
                from(
                    Paths.get(
                        flutterRoot!!.absolutePath,
                        "packages",
                        "flutter_tools",
                        "gradle",
                        "src",
                        "main",
                        "scripts",
                        "native_plugin_loader.gradle.kts"
                    )
                )
            }
    
            val flutterExtension: FlutterExtension =
                project.extensions.create("flutter", FlutterExtension::class.java)
    
            // TODO(gmackall): is this actually a different properties file than the previous one?
            val rootProjectLocalProperties = Properties()
            val rootProjectLocalPropertiesFile = rootProject.file("local.properties")
            if (rootProjectLocalPropertiesFile.exists()) {
                rootProjectLocalPropertiesFile.reader(StandardCharsets.UTF_8).use { reader ->
                    rootProjectLocalProperties.load(reader)
                }
            }
            flutterExtension.flutterVersionCode =
                rootProjectLocalProperties.getProperty("flutter.versionCode", "1")
            flutterExtension.flutterVersionName =
                rootProjectLocalProperties.getProperty("flutter.versionName", "1.0")
    
            this.addFlutterTasks(project)
    
            // By default, assembling APKs generates fat APKs if multiple platforms are passed.
            // Configuring split per ABI allows to generate separate APKs for each abi.
            // This is a noop when building a bundle.
            if (FlutterPluginUtils.shouldProjectSplitPerAbi(project)) {
                FlutterPluginUtils.getAndroidExtension(project).splits.abi {
                    isEnable = true
                    reset()
                    isUniversalApk = false
                }
            } else {
                // When splits-per-abi is NOT enabled, configure abiFilters to control which
                // native libraries are included in the APK.
                //
                // This is crucial: If a project includes third-party dependencies with x86 native libraries,
                // without these abiFilters, Google Play would incorrectly identify the app as supporting x86.
                // When users with x86 devices install the app, it would crash at runtime because Flutter's
                // native libraries aren't available for x86. By filtering out x86 at build time, Google Play
                // correctly excludes x86 devices from the compatible device list.
                //
                // NOTE: This code does NOT affect "add-to-app" scenarios because:
                // 1. For 'flutter build aar': abiFilters have no effect since libflutter.so and libapp.so
                //    are not packaged into AAR artifacts - they are only added as dependencies
                //    in pom files.
                // 2. For project dependencies (implementation(project(":flutter"))): The Flutter
                //    Gradle Plugin is not applied to the main app subproject, so this apply()
                //    method is never called.
                //
                // abiFilters cannot be added to templates because it would break builds when
                // --splits-per-abi is used due to conflicting configuration. This approach
                // adds them programmatically only when splits are not configured.
                //
                // If the user has specified abiFilters in their build.gradle file, those
                // settings will take precedence over these defaults.
                project.afterEvaluate {
                    FlutterPluginUtils.getAndroidExtension(project).buildTypes.forEach { buildType ->
                        if (buildType.ndk.abiFilters.isEmpty()) {
                            FlutterPluginConstants.DEFAULT_PLATFORMS.forEach { platform ->
                                val abiValue: String =
                                    FlutterPluginConstants.PLATFORM_ARCH_MAP[platform]
                                        ?: throw GradleException("Invalid platform: $platform")
                                buildType.ndk.abiFilters.add(abiValue)
                            }
                        }
                    }
                }
            }
            val propDeferredComponentNames = "deferred-component-names"
            val deferredComponentNamesValue: String? =
                project.findProperty(propDeferredComponentNames) as? String
            if (deferredComponentNamesValue != null) {
                val componentNames: Set<String> =
                    deferredComponentNamesValue
                        .split(',')
                        .map { ":$it" }
                        .toSet()
                // TODO(gmackall): Unify the types we use for the android extension. This is yet
                //   another type we need unfortunately.
                val androidExtensionAsApplicationExtension =
                    project.extensions.getByType(ApplicationExtension::class.java)
                // TODO(gmackall): Should we clear here? I think this is equivalent to what we used to
                //    do, but unsure. Can't use a closure.
                androidExtensionAsApplicationExtension.dynamicFeatures.clear()
                androidExtensionAsApplicationExtension.dynamicFeatures.addAll(componentNames)
            }
    
            FlutterPluginUtils.getTargetPlatforms(project).forEach { targetArch ->
                val abiValue: String? = FlutterPluginConstants.PLATFORM_ARCH_MAP[targetArch]
                val androidExtension: BaseExtension = FlutterPluginUtils.getAndroidExtension(project)
                androidExtension.splits.abi.include(abiValue!!)
            }
    
            val flutterExecutableName = getExecutableNameForPlatform("flutter")
            flutterExecutable =
                Paths.get(flutterRoot!!.absolutePath, "bin", flutterExecutableName).toFile()
    
            // Validate that the provided Gradle, Java, AGP, and KGP versions are all within our
            // supported range.
            val shouldSkipDependencyChecks: Boolean =
                project.hasProperty("skipDependencyChecks") &&
                    (
                        project.properties["skipDependencyChecks"] as? Boolean
                            ?: false
                    )
            if (!shouldSkipDependencyChecks) {
                try {
                    DependencyVersionChecker.checkDependencyVersions(project)
                } catch (e: Exception) {
                    if (!project.hasProperty("usesUnsupportedDependencyVersions") ||
                        !(project.properties["usesUnsupportedDependencyVersions"] as Boolean)
                    ) {
                        // Possible bug in dependency checking code - warn and do not block build.
                        project.logger.error(
                            "Warning: Flutter was unable to detect project Gradle, Java, " +
                                "AGP, and KGP versions. Skipping dependency version checking. Error was: " +
                                e
                        )
                    } else {
                        // If usesUnsupportedDependencyVersions is set, the exception was thrown by us
                        // in the dependency version checker plugin so re-throw it here.
                        throw e
                    }
                }
            }
    
            BaseApplicationNameHandler.setBaseName(project)
            val flutterProguardRules: String =
                Paths
                    .get(
                        flutterRoot!!.absolutePath,
                        "packages",
                        "flutter_tools",
                        "gradle",
                        "flutter_proguard_rules.pro"
                    ).toString()
            // TODO(gmackall): reconsider getting the android extension every time
            FlutterPluginUtils.getAndroidExtension(project).buildTypes {
                // Add profile build type.
                create("profile") {
                    initWith(getByName("debug"))
                    // TODO(gmackall): do we need to clear?
                    this.matchingFallbacks.clear()
                    this.matchingFallbacks.addAll(listOf("debug", "release"))
                }
    
                // TODO(garyq): Shrinking is only false for multi apk split aot builds, where shrinking is not allowed yet.
                // This limitation has been removed experimentally in gradle plugin version 4.2, so we can remove
                // this check when we upgrade to 4.2+ gradle. Currently, deferred components apps may see
                // increased app size due to this.
                if (FlutterPluginUtils.shouldShrinkResources(project)) {
                    getByName("release") {
                        isMinifyEnabled = true
                        // Enables resource shrinking, which is performed by the Android Gradle plugin.
                        // The resource shrinker can't be used for libraries.
                        isShrinkResources = FlutterPluginUtils.isBuiltAsApp(project)
                        // Fallback to `android/app/proguard-rules.pro`.
                        // This way, custom Proguard rules can be configured as needed.
                        proguardFiles(
                            FlutterPluginUtils
                                .getAndroidExtension(project)
                                .getDefaultProguardFile("proguard-android-optimize.txt"),
                            flutterProguardRules,
                            "proguard-rules.pro"
                        )
                    }
                }
            }
    
            FlutterPluginUtils.forceNdkDownload(project, flutterRootPath)
    
            if (FlutterPluginUtils.shouldProjectUseLocalEngine(project)) {
                // This is required to pass the local engine to flutter build aot.
                val engineOutPath: String = project.properties["local-engine-out"] as String
                val engineOut: File = project.file(engineOutPath)
                if (!engineOut.isDirectory) {
                    throw GradleException("local-engine-out must point to a local engine build")
                }
                localEngine = engineOut.name
                localEngineSrcPath = engineOut.parentFile.parent
    
                val engineHostOutPath: String = project.properties["local-engine-host-out"] as String
                val engineHostOut: File = project.file(engineHostOutPath)
                if (!engineHostOut.isDirectory) {
                    throw GradleException("local-engine-host-out must point to a local engine host build")
                }
                localEngineHost = engineHostOut.name
            }
            FlutterPluginUtils.getAndroidExtension(project).buildTypes.all {
                addFlutterDependencies(this)
            }
        }
    
        private fun addFlutterDependencies(buildType: com.android.builder.model.BuildType) {
            FlutterPluginUtils.addFlutterDependencies(
                project!!,
                buildType,
                getPluginHandler(project!!),
                engineVersion!!
            )
        }
    
        private fun getExecutableNameForPlatform(baseExecutableName: String): String =
            if (OperatingSystem.current().isWindows) "$baseExecutableName.bat" else baseExecutableName
    
        private fun resolveFlutterSdkProperty(defaultValue: String?): String? {
            val propertyName = "flutter.sdk"
            if (localProperties == null) {
                localProperties =
                    readPropertiesIfExist(File(project!!.projectDir.parentFile, "local.properties"))
            }
            return project?.findProperty(propertyName) as? String ?: localProperties!!.getProperty(
                propertyName,
                defaultValue
            )
        }
    
        private fun addTaskForLockfileGeneration(rootProject: Project) {
            try {
                rootProject.tasks.register("generateLockfiles") {
                    doLast {
                        rootProject.subprojects.forEach { subproject ->
                            val gradlew: String =
                                getExecutableNameForPlatform("${rootProject.projectDir}/gradlew")
                            val execOps = rootProject.serviceOf<ExecOperations>()
                            execOps.exec {
                                workingDir(rootProject.projectDir)
                                executable(gradlew)
                                args(":${subproject.name}:dependencies", "--write-locks")
                            }
                        }
                    }
                }
            } catch (e: TaskInstantiationException) {
                // ignored
            }
        }
    
        private fun addFlutterTasks(projectToAddTasksTo: Project) {
            if (projectToAddTasksTo.state.failure != null) {
                return
            }
    
            FlutterPluginUtils.addTaskForJavaVersion(projectToAddTasksTo)
            FlutterPluginUtils.addTaskForKGPVersion(projectToAddTasksTo)
            if (FlutterPluginUtils.isFlutterAppProject(projectToAddTasksTo)) {
                FlutterPluginUtils.addTaskForPrintBuildVariants(projectToAddTasksTo)
                FlutterPluginUtils.addTasksForOutputsAppLinkSettings(projectToAddTasksTo)
            }
    
            val targetPlatforms: List<String> =
                FlutterPluginUtils.getTargetPlatforms(projectToAddTasksTo)
    
            val flutterPlugin = this
    
            if (FlutterPluginUtils.isFlutterAppProject(projectToAddTasksTo)) {
                // TODO(gmackall): I think this can be BaseExtension, with findByType.
                val android: AbstractAppExtension =
                    projectToAddTasksTo.extensions.findByName("android") as AbstractAppExtension
                android.applicationVariants.configureEach {
                    val variant = this
                    val assembleTask = variant.assembleProvider.get()
                    if (!FlutterPluginUtils.shouldConfigureFlutterTask(
                            projectToAddTasksTo,
                            assembleTask
                        )
                    ) {
                        return@configureEach
                    }
                    val copyFlutterAssetsTask: Task =
                        addFlutterDeps(variant, flutterPlugin, targetPlatforms)
    
                    // TODO(gmackall): Migrate to AGPs variant api.
                    //    https://github.com/flutter/flutter/issues/166550
                    @Suppress("DEPRECATION")
                    val variantOutput: com.android.build.gradle.api.BaseVariantOutput = variant.outputs.first()
                    val processResources: ProcessAndroidResources =
                        try {
                            variantOutput.processResourcesProvider.get()
                        } catch (e: UnknownTaskException) {
                            // TODO(gmackall): Migrate to AGPs variant api.
                            //    https://github.com/flutter/flutter/issues/166550
                            @Suppress("DEPRECATION")
                            variantOutput.processResources
                        }
                    processResources.dependsOn(copyFlutterAssetsTask)
    
                    // Copy the output APKs into a known location, so `flutter run` or `flutter build apk`
                    // can discover them. By default, this is `<app-dir>/build/app/outputs/flutter-apk/<filename>.apk`.
                    //
                    // The filename consists of `app<-abi>?<-flavor-name>?-<build-mode>.apk`.
                    // Where:
                    //   * `abi` can be `armeabi-v7a|arm64-v8a|x86_64` only if the flag `split-per-abi` is set.
                    //   * `flavor-name` is the flavor used to build the app in lower case if the assemble task is called.
                    //   * `build-mode` can be `release|debug|profile`.
                    variant.outputs.forEach { output ->
                        assembleTask.doLast {
                            // TODO(gmackall): Migrate to AGPs variant api.
                            //    https://github.com/flutter/flutter/issues/166550
                            @Suppress("DEPRECATION")
                            output as com.android.build.gradle.api.ApkVariantOutput
                            val packageApplicationProvider: PackageAndroidArtifact =
                                variant.packageApplicationProvider.get()
                            val outputDirectory: Directory =
                                packageApplicationProvider.outputDirectory.get()
                            val outputDirectoryStr: String = outputDirectory.toString()
                            var filename = "app"
    
                            // TODO(gmackall): Migrate to AGPs variant api.
                            //    https://github.com/flutter/flutter/issues/166550
                            @Suppress("DEPRECATION")
                            val abi = output.getFilter(com.android.build.VariantOutput.FilterType.ABI)
                            if (abi != null && abi.isNotEmpty()) {
                                filename += "-$abi"
                            }
                            if (variant.flavorName != null && variant.flavorName.isNotEmpty()) {
                                filename += "-${FlutterPluginUtils.lowercase(variant.flavorName)}"
                            }
                            filename += "-${FlutterPluginUtils.buildModeFor(variant.buildType)}"
                            projectToAddTasksTo.copy {
                                from(File("$outputDirectoryStr/${output.outputFileName}"))
                                into(
                                    File(
                                        "${
                                            projectToAddTasksTo.layout.buildDirectory.dir("outputs/flutter-apk")
                                                .get()
                                        }"
                                    )
                                )
                                rename { "$filename.apk" }
                            }
                        }
                    }
                }
                // Copy the native assets created by build.dart and placed here by flutter assemble.
                // This path is not flavor specific and must only be added once.
                // If support for flavors is added to native assets, then they must only be added
                // once per flavor; see https://github.com/dart-lang/native/issues/1359.
                val nativeAssetsDir =
                    "${projectToAddTasksTo.layout.buildDirectory.get()}/../native_assets/android/jniLibs/lib/"
                android.sourceSets
                    .getByName("main")
                    .jniLibs
                    .srcDir(nativeAssetsDir)
                getPluginHandler(projectToAddTasksTo).configurePlugins(engineVersion!!)
                FlutterPluginUtils.detectLowCompileSdkVersionOrNdkVersion(
                    projectToAddTasksTo,
                    getPluginHandler(projectToAddTasksTo).getPluginList()
                )
                return
            }
            // Flutter host module project (Add-to-app).
            val hostAppProjectName: String? =
                if (projectToAddTasksTo.rootProject.hasProperty("flutter.hostAppProjectName")) {
                    projectToAddTasksTo.rootProject.property(
                        "flutter.hostAppProjectName"
                    ) as? String
                } else {
                    "app"
                }
            val appProject: Project? =
                projectToAddTasksTo.rootProject.findProject(":$hostAppProjectName")
            check(appProject != null) {
                "Project :$hostAppProjectName doesn't exist. To customize the host app project name, set `flutter.hostAppProjectName=<project-name>` in gradle.properties."
            }
            // Wait for the host app project configuration.
            appProject.afterEvaluate {
                val androidLibraryExtension =
                    projectToAddTasksTo.extensions.findByType(LibraryExtension::class.java)
                check(androidLibraryExtension != null)
                androidLibraryExtension.libraryVariants.all libraryVariantAll@{
                    val libraryVariant = this
                    var copyFlutterAssetsTask: Task? = null
                    val androidAppExtension =
                        appProject.extensions.findByName("android") as? AbstractAppExtension
                    check(androidAppExtension != null)
                    androidAppExtension.applicationVariants.all applicationVariantAll@{
                        val appProjectVariant = this
                        val appAssembleTask: Task = appProjectVariant.assembleProvider.get()
                        if (!FlutterPluginUtils.shouldConfigureFlutterTask(project, appAssembleTask)) {
                            return@applicationVariantAll
                        }
    
                        // Find a compatible application variant in the host app.
                        //
                        // For example, consider a host app that defines the following variants:
                        // | ----------------- | ----------------------------- |
                        // |   Build Variant   |   Flutter Equivalent Variant  |
                        // | ----------------- | ----------------------------- |
                        // |   freeRelease     |   release                     |
                        // |   freeDebug       |   debug                       |
                        // |   freeDevelop     |   debug                       |
                        // |   profile         |   profile                     |
                        // | ----------------- | ----------------------------- |
                        //
                        // This mapping is based on the following rules:
                        // 1. If the host app build variant name is `profile` then the equivalent
                        //    Flutter variant is `profile`.
                        // 2. If the host app build variant is debuggable
                        //    (e.g. `buildType.debuggable = true`), then the equivalent Flutter
                        //    variant is `debug`.
                        // 3. Otherwise, the equivalent Flutter variant is `release`.
                        val variantBuildMode: String =
                            FlutterPluginUtils.buildModeFor(libraryVariant.buildType)
                        if (FlutterPluginUtils.buildModeFor(appProjectVariant.buildType) != variantBuildMode) {
                            return@applicationVariantAll
                        }
                        copyFlutterAssetsTask = copyFlutterAssetsTask ?: addFlutterDeps(
                            libraryVariant,
                            flutterPlugin,
                            targetPlatforms
                        )
                        // TODO(gmackall): Migrate to AGPs variant api.
                        //    https://github.com/flutter/flutter/issues/166550
                        val mergeAssets =
                            projectToAddTasksTo
                                .tasks
                                .findByPath(":$hostAppProjectName:merge${FlutterPluginUtils.capitalize(appProjectVariant.name)}Assets")
                        check(mergeAssets != null)
                        mergeAssets.dependsOn(copyFlutterAssetsTask)
                    }
                }
            }
            getPluginHandler(projectToAddTasksTo).configurePlugins(engineVersion!!)
            FlutterPluginUtils.detectLowCompileSdkVersionOrNdkVersion(
                projectToAddTasksTo,
                getPluginHandler(projectToAddTasksTo).getPluginList()
            )
        }
    
        private fun getPluginHandler(project: Project): PluginHandler {
            if (this.pluginHandler == null) {
                this.pluginHandler = PluginHandler(project)
            }
            return this.pluginHandler!!
        }
    
        companion object {
            const val PROP_LOCAL_ENGINE_REPO: String = "local-engine-repo"
    
            /**
             * The name prefix for flutter builds. This is used to identify gradle tasks
             * where we expect the flutter tool to provide any error output, and skip the
             * standard Gradle error output in the FlutterEventLogger. If you change this,
             * be sure to change any instances of this string in symbols in the code below
             * to match.
             */
            private const val FLUTTER_BUILD_PREFIX: String = "flutterBuild"
    
            /**
             * Finds a task by name, returning null if the task does not exist.
             */
            private fun findTaskOrNull(
                project: Project,
                taskName: String
            ): Task? =
                try {
                    project.tasks.named(taskName).get()
                } catch (ignored: UnknownTaskException) {
                    null
                }
    
            // TODO(gmackall): Migrate to AGPs variant api.
            //    https://github.com/flutter/flutter/issues/166550
            private fun addFlutterDeps(
                @Suppress("DEPRECATION") variant: com.android.build.gradle.api.BaseVariant,
                flutterPlugin: FlutterPlugin,
                targetPlatforms: List<String>
            ): Task {
                // Shorthand
                val project: Project = flutterPlugin.project!!
    
                val fileSystemRootsValue: Array<String>? =
                    project
                        .findProperty("filesystem-roots")
                        ?.toString()
                        ?.split("\\|")
                        ?.toTypedArray()
                val fileSystemSchemeValue: String? =
                    project.findProperty("filesystem-scheme")?.toString()
                val trackWidgetCreationValue: Boolean =
                    project.findProperty("track-widget-creation")?.toString()?.toBoolean() ?: true
                val frontendServerStarterPathValue: String? =
                    project.findProperty("frontend-server-starter-path")?.toString()
                val extraFrontEndOptionsValue: String? =
                    project.findProperty("extra-front-end-options")?.toString()
                val extraGenSnapshotOptionsValue: String? =
                    project.findProperty("extra-gen-snapshot-options")?.toString()
                val splitDebugInfoValue: String? = project.findProperty("split-debug-info")?.toString()
                val dartObfuscationValue: Boolean =
                    project.findProperty("dart-obfuscation")?.toString()?.toBoolean() ?: false
                val treeShakeIconsOptionsValue: Boolean =
                    project.findProperty("tree-shake-icons")?.toString()?.toBoolean() ?: false
                val dartDefinesValue: String? = project.findProperty("dart-defines")?.toString()
                val performanceMeasurementFileValue: String? =
                    project.findProperty("performance-measurement-file")?.toString()
                val codeSizeDirectoryValue: String? =
                    project.findProperty("code-size-directory")?.toString()
                val deferredComponentsValue: Boolean =
                    project.findProperty("deferred-components")?.toString()?.toBoolean() ?: false
                val validateDeferredComponentsValue: Boolean =
                    project.findProperty("validate-deferred-components")?.toString()?.toBoolean() ?: true
    
                if (FlutterPluginUtils.shouldProjectSplitPerAbi(project)) {
                    variant.outputs.forEach { output ->
                        // need to force this as the API does not return the right thing for our use.
                        // TODO(gmackall): Migrate to AGPs variant api.
                        //    https://github.com/flutter/flutter/issues/166550
                        @Suppress("DEPRECATION")
                        output as com.android.build.gradle.api.ApkVariantOutput
                        // TODO(gmackall): Migrate to AGPs variant api.
                        //    https://github.com/flutter/flutter/issues/166550
                        @Suppress("DEPRECATION")
                        val filterIdentifier: String? =
                            output.getFilter(com.android.build.VariantOutput.FilterType.ABI)
                        val abiVersionCode: Int? = FlutterPluginConstants.ABI_VERSION[filterIdentifier]
                        if (abiVersionCode != null) {
                            output.versionCodeOverride = abiVersionCode * 1000 + variant.mergedFlavor.versionCode as Int
                        }
                    }
                }
    
                // Build an AAR when this property is defined.
                val isBuildingAar: Boolean = project.hasProperty("is-plugin")
                // In add to app scenarios, a Gradle project contains a `:flutter` and `:app` project.
                // `:flutter` is used as a subproject when these tasks exists and the build isn't building an AAR.
                // TODO(gmackall): I think this is just always null? Which is great news! Consider removing.
                val packageAssets: Task? =
                    findTaskOrNull(
                        project,
                        "package${FlutterPluginUtils.capitalize(variant.name)}Assets"
                    )
                val cleanPackageAssets: Task? =
                    findTaskOrNull(
                        project,
                        "cleanPackage${FlutterPluginUtils.capitalize(variant.name)}Assets"
                    )
    
                val isUsedAsSubproject: Boolean =
                    packageAssets != null && cleanPackageAssets != null && !isBuildingAar
    
                val variantBuildMode: String = FlutterPluginUtils.buildModeFor(variant.buildType)
                val flavorValue: String = variant.flavorName
                val taskName: String =
                    FlutterPluginUtils.toCamelCase(
                        listOf(
                            "compile",
                            FLUTTER_BUILD_PREFIX,
                            variant.name
                        )
                    )
                // The task provider below will shadow a lot of the variable names, so provide this reference
                // to access them within that scope.
    
                // Be careful when configuring task below, Groovy has bizarre
                // scoping rules: writing `verbose isVerbose()` means calling
                // `isVerbose` on the task itself - which would return `verbose`
                // original value. You either need to hoist the value
                // into a separate variable `verbose verboseValue` or prefix with
                // `this` (`verbose this.isVerbose()`).
                val compileTaskProvider: TaskProvider<FlutterTask> =
                    project.tasks.register(taskName, FlutterTask::class.java) {
                        flutterRoot = flutterPlugin.flutterRoot
                        flutterExecutable = flutterPlugin.flutterExecutable
                        buildMode = variantBuildMode
                        minSdkVersion = variant.mergedFlavor.minSdkVersion!!.apiLevel
                        localEngine = flutterPlugin.localEngine
                        localEngineHost = flutterPlugin.localEngineHost
                        localEngineSrcPath = flutterPlugin.localEngineSrcPath
                        targetPath = FlutterPluginUtils.getFlutterTarget(project)
                        verbose = FlutterPluginUtils.isProjectVerbose(project)
                        fastStart = FlutterPluginUtils.isProjectFastStart(project)
                        fileSystemRoots = fileSystemRootsValue
                        fileSystemScheme = fileSystemSchemeValue
                        trackWidgetCreation = trackWidgetCreationValue
                        targetPlatformValues = targetPlatforms
                        sourceDir = FlutterPluginUtils.getFlutterSourceDirectory(project)
                        intermediateDir =
                            project.file(
                                project.layout.buildDirectory.dir("${FlutterPluginConstants.INTERMEDIATES_DIR}/flutter/${variant.name}/")
                            )
                        frontendServerStarterPath = frontendServerStarterPathValue
                        extraFrontEndOptions = extraFrontEndOptionsValue
                        extraGenSnapshotOptions = extraGenSnapshotOptionsValue
                        splitDebugInfo = splitDebugInfoValue
                        treeShakeIcons = treeShakeIconsOptionsValue
                        dartObfuscation = dartObfuscationValue
                        dartDefines = dartDefinesValue
                        performanceMeasurementFile = performanceMeasurementFileValue
                        codeSizeDirectory = codeSizeDirectoryValue
                        deferredComponents = deferredComponentsValue
                        validateDeferredComponents = validateDeferredComponentsValue
                        flavor = flavorValue
                    }
                val compileTask: FlutterTask = compileTaskProvider.get()
                val libJar: File =
                    project.file(
                        project.layout.buildDirectory.dir("${FlutterPluginConstants.INTERMEDIATES_DIR}/flutter/${variant.name}/libs.jar")
                    )
                val packJniLibsTaskProvider: TaskProvider<Jar> =
                    project.tasks.register(
                        "packJniLibs${FLUTTER_BUILD_PREFIX}${FlutterPluginUtils.capitalize(variant.name)}",
                        Jar::class.java
                    ) {
                        destinationDirectory.set(libJar.parentFile)
                        archiveFileName.set(libJar.name)
                        dependsOn(compileTask)
                        targetPlatforms.forEach { targetPlatform ->
                            val abi: String? = FlutterPluginConstants.PLATFORM_ARCH_MAP[targetPlatform]
                            from("${compileTask.intermediateDir}/$abi") {
                                include("*.so")
                                // Move `app.so` to `lib/<abi>/libapp.so`
                                rename { filename: String -> "lib/$abi/lib$filename" }
                            }
                            // Copy the native assets created by build.dart and placed in build/native_assets by flutter assemble.
                            // The `$project.layout.buildDirectory` is '.android/Flutter/build/' instead of 'build/'.
                            val buildDir =
                                "${FlutterPluginUtils.getFlutterSourceDirectory(project)}/build"
                            val nativeAssetsDir =
                                "$buildDir/native_assets/android/jniLibs/lib"
                            from("$nativeAssetsDir/$abi") {
                                include("*.so")
                                rename { filename: String -> "lib/$abi/$filename" }
                            }
                        }
                    }
                val packJniLibsTask: Task = packJniLibsTaskProvider.get()
                FlutterPluginUtils.addApiDependencies(
                    project,
                    variant.name,
                    project.files({
                        packJniLibsTask
                    })
                )
                val copyFlutterAssetsTaskProvider: TaskProvider<Copy> =
                    project.tasks.register(
                        "copyFlutterAssets${FlutterPluginUtils.capitalize(variant.name)}",
                        Copy::class.java
                    ) {
                        dependsOn(compileTask)
                        with(compileTask.assets)
                        filePermissions {
                            user {
                                read = true
                                write = true
                            }
                        }
                        if (isUsedAsSubproject) {
                            // TODO(gmackall): above is always false, can delete
                            dependsOn(packageAssets)
                            dependsOn(cleanPackageAssets)
                            into(packageAssets!!.outputs)
                        }
                        val mergeAssets =
                            try {
                                variant.mergeAssetsProvider.get()
                            } catch (e: IllegalStateException) {
                                // TODO(gmackall): Migrate to AGPs variant api.
                                //    https://github.com/flutter/flutter/issues/166550
                                @Suppress("DEPRECATION")
                                variant.mergeAssets
                            }
                        dependsOn(mergeAssets)
                        dependsOn("clean${FlutterPluginUtils.capitalize(mergeAssets.name)}")
                        mergeAssets.mustRunAfter("clean${FlutterPluginUtils.capitalize(mergeAssets.name)}")
                        into(mergeAssets.outputDir)
                    }
                val copyFlutterAssetsTask: Task = copyFlutterAssetsTaskProvider.get()
                if (!isUsedAsSubproject) {
                    // TODO(gmackall): Migrate to AGPs variant api.
                    //    https://github.com/flutter/flutter/issues/166550
                    @Suppress("DEPRECATION")
                    val variantOutput: com.android.build.gradle.api.BaseVariantOutput = variant.outputs.first()
                    val processResources =
                        try {
                            variantOutput.processResourcesProvider.get()
                        } catch (e: IllegalStateException) {
                            // TODO(gmackall): Migrate to AGPs variant api.
                            //    https://github.com/flutter/flutter/issues/166550
                            @Suppress("DEPRECATION")
                            variantOutput.processResources
                        }
                    processResources.dependsOn(copyFlutterAssetsTask)
                }
                // The following tasks use the output of copyFlutterAssetsTask,
                // so it's necessary to declare it as an dependency since Gradle 8.
                // See https://docs.gradle.org/8.1/userguide/validation_problems.html#implicit_dependency.
                val tasksToCheck =
                    listOf(
                        "compress${FlutterPluginUtils.capitalize(variant.name)}Assets",
                        "bundle${FlutterPluginUtils.capitalize(variant.name)}Aar",
                        "bundle${FlutterPluginUtils.capitalize(variant.name)}LocalLintAar"
                    )
                tasksToCheck.forEach { taskTocheck ->
                    try {
                        project.tasks.named(taskTocheck).configure {
                            dependsOn(copyFlutterAssetsTask)
                        }
                    } catch (ignored: UnknownTaskException) {
                        // ignored
                    }
                }
                return copyFlutterAssetsTask
            }
        }
    
        /**
         * Returns true if the Gradle task is invoked by Android Studio.
         *
         * This is true when the property `android.injected.invoked.from.ide` is passed to Gradle.
         * This property is set by Android Studio when it invokes a Gradle task.
         */
        private fun isInvokedFromAndroidStudio(): Boolean = project?.hasProperty("android.injected.invoked.from.ide") == true
    }
    
  10. komaxx commented on Aug 20, 2025

    @komaxx

    @angcyo
    Ok, thank you! It's a downgrade to a previous Flutter version for me then. Getting all developer machines plus CI servers patched is waaay too much of a hassle

  11. angcyo commented on Aug 20, 2025

    @angcyo
    Author

    @angcyo Ok, thank you! It's a downgrade to a previous Flutter version for me then. Getting all developer machines plus CI servers patched is waaay too much of a hassle

    Downgrading can also solve the problem.

  12. reidbaker commented on Aug 20, 2025

    @reidbaker
    Contributor

    Sorry, I'm confused what the solution is here. We used to limit abis by just adding a line in gradle

    ndk.abiFilters "armeabi-v7a", "arm64-v8a"
    

    Now, this is no longer working. Is there a new way to achieve the same without modifying the source code of the plugin?

    Setting abiFilters should still work (and does in our testing). The issue tracked here was the assumption that abiFilters was an empty list and that callingadd one time would result in an abi list of length 1. Now if you wish to control the list using add you must clear the list first which does not require a flutter change. Most importantly the default is now correct for most flutter apps.

  13. angcyo commented on Aug 21, 2025

    @angcyo
    Author

    Sorry, I'm confused what the solution is here. We used to limit abis by just adding a line in gradle

    ndk.abiFilters "armeabi-v7a", "arm64-v8a"
    

    Now, this is no longer working. Is there a new way to achieve the same without modifying the source code of the plugin?

    Setting abiFilters should still work (and does in our testing). The issue tracked here was the assumption that abiFilters was an empty list and that callingadd one time would result in an abi list of length 1. Now if you wish to control the list using add you must clear the list first which does not require a flutter change. Most importantly the default is now correct for most flutter apps.

    Here is a minimum verification problem project : https://github.com/angcyo/flutter_abi_filters_demo

  14. amei714 commented on Aug 21, 2025

    @amei714

    ndk.abiFilters.clear()
    ndk.abiFilters "armeabi-v7a", "arm64-v8a"

  15. added
    r: fixedIssue is closed as already fixed in a newer version
    on Aug 27, 2025
  16. gujintao1900 commented on Aug 29, 2025

    @gujintao1900

    workaround move ndk config from defaultConfig to buildTypes

    buildTypes {
        debug {
            ndk {
                abiFilters.clear()
                abiFilters "arm64-v8a"
            }
            signingConfig = signingConfigs.release
            minifyEnabled = false
           
            proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
        }
        release {
            ndk {
                abiFilters.clear()
                abiFilters "arm64-v8a"
            }
            signingConfig = signingConfigs.release
            minifyEnabled = true
            proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
        }
    }
  17. camsim99 commented on Sep 2, 2025

    @camsim99
    Contributor
  18. github-actions commented on Sep 16, 2025

    @github-actions

    This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v and a minimal reproduction of the issue.

  19. locked as resolved and limited conversation to collaborators on Sep 16, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

P1High-priority issues at the top of the work listd: docs/flutter/flutter/docs, for contributorsfound in release: 3.33Found to occur in 3.33found in release: 3.35Found to occur in 3.35has reproducible stepsThe issue has been confirmed reproducible and is ready to work onplatform-androidAndroid applications specificallyr: fixedIssue is closed as already fixed in a newer versionteam-androidOwned by Android platform teamtriaged-androidTriaged by Android platform team

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions