Skip to content

feat: adopt AGP v9#57038

Open
hurali97 wants to merge 6 commits into
facebook:mainfrom
hurali97:bump-to-agp9
Open

feat: adopt AGP v9#57038
hurali97 wants to merge 6 commits into
facebook:mainfrom
hurali97:bump-to-agp9

Conversation

@hurali97
Copy link
Copy Markdown
Contributor

@hurali97 hurali97 commented Jun 2, 2026

Summary:

This PR is part of Android Gradle Plugin v9 adoption. With this PR, we adopt the AGP9 repository wide and address the deprecated APIs with the newer ones.

This is in combination with #57038

Changes to `configureBuildConfigFieldsForLibraries` and `configureNamespaceForLibraries`
These helpers are called from `ReactPlugin.apply`, but they currently iterate over `rootProject.allprojects`. Since `com.facebook.react` is applied by multiple Android library projects, each application of the plugin repeats the same traversal and attempts to register `finalizeDsl` callbacks for every Android library project.

The first traversal can register the callbacks successfully. However, a later traversal can reach a project whose Android DSL finalization phase has already completed. Starting with AGP 9, AGP explicitly errors when finalizeDsl is called after that phase has passed, which causes the build failure. see

To understand this, consider following modules:

:react-native-safe-area-context
:react-native-mmkv
:react-native-skia

The current code on main, would result in executing the following block 3 times:

  fun configureBuildConfigFieldsForLibraries(appProject: Project) {
    appProject.rootProject.allprojects { subproject ->
      println("=== subproject ${subproject.name}")
      subproject.pluginManager.withPlugin("com.android.library") {
        subproject.extensions
            .getByType(LibraryAndroidComponentsExtension::class.java)
            .finalizeDsl { ext -> ext.buildFeatures.buildConfig = true }
      }
   }
}

The output would be:

> Configure project: react-native-safe-area-context
=== subproject RNProject
=== subproject app
=== subproject react-native-safe-area-context
=== subproject react-native-mmkv
=== subproject react-native-skia

> Configure project: react-native-mmkv
=== subproject RNProject
=== subproject app
=== subproject react-native-safe-area-context
=== subproject react-native-mmkv
=== subproject react-native-skia

> Configure project: react-native-skia
=== subproject RNProject
=== subproject app
=== subproject react-native-safe-area-context
=== subproject react-native-mmkv
=== subproject react-native-skia

Now with AGP9, the same code will throw this error:

> Configure project: react-native-safe-area-context
=== subproject RNProject
=== subproject app
=== subproject react-native-safe-area-context
=== subproject react-native-mmkv
=== subproject react-native-skia

> Configure project: react-native-mmkv
=== subproject RNProject
=== subproject app
=== subproject react-native-safe-area-context
=== subproject react-native-mmkv

* What went wrong:
A problem occurred evaluating project': react-native-mmkv'.
> Failed to apply plugin
'com. facebook. react'
> It is too late to call
finalizeDst
as the DSL finalization
blocks have already been executed. \n
In particular, you cannot call 'finalizeDsl' within the "beforeVariants' or "onVariants' blocks.
Instead, you must call finalizeDsl' directly within the
"androidComponents' block

Solution:

Since these helpers are only intended to configure Android library projects that apply the React plugin, we can configure the current project inside pluginManager.withPlugin("com.android.library") instead of repeatedly configuring all projects from every plugin application.

 fun configureBuildConfigFieldsForLibraries(project: Project) {
    project.extensions
      .getByType(LibraryAndroidComponentsExtension::class.java)
      .finalizeDsl { ext ->
        ext.buildFeatures.buildConfig = true
      }
    }
  fun configureNamespaceForLibraries(project: Project) {
    project.extensions
      .getByType(LibraryAndroidComponentsExtension::class.java)
      .finalizeDsl { ext ->
        if (ext.namespace == null) {
          val manifestFile =
            project.layout.projectDirectory.file("src/main/AndroidManifest.xml").asFile
          manifestFile
            .takeIf { it.exists() }
            ?.let { file ->
              getPackageNameFromManifest(file)?.let { packageName ->
                ext.namespace = packageName
              }
            }
        }
      }
    }

One important note for configureNamespaceForLibraries is that the old com.android.build.gradle.LibraryExtension is deprecated in favor of com.android.build.api.dsl.LibraryExtension - which does not expose manifestFile from the sourceSets. So we have to define a path for it. For most libraries, this should be OK but for the ones which define custom path, this will fail.

Hence, I believe, if it's important to have this fallback, we take this tradeoff. Otherwise, since there has been 2 years passed for AGP8 adoption and the time when this fallback was added, we may safely remove this function as almost all libraries now define a namespace in android { } DSL.

With the above in place, we make one final change and that is to move these two functions inside the withPlugin("com.android.library") as these are only intended for libraries and that way, we also ensure that these will only be applied once the com.android.library plugin has been applied.

    project.pluginManager.withPlugin("com.android.library") {
      configureBuildConfigFieldsForLibraries(project)
      configureNamespaceForLibraries(project)
      configureCodegen(project, extension, rootExtension, isLibrary = true)
    }

Changelog:

[ANDROID] [BREAKING] - Adopt AGP v9

Test Plan:

  • CI passes
  • Verified on RN Tester
  • Verified on Local RN App
rn-tester.mp4

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Jun 2, 2026
androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" }
androidx-appcompat-resources = { module = "androidx.appcompat:appcompat-resources", version.ref = "androidx-appcompat" }
androidx-autofill = { module = "androidx.autofill:autofill", version.ref = "androidx-autofill" }
androidx-collection = { module = "androidx.collection:collection", version.ref = "androidx-collection" }
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is required for ReactAndroid to compile successfully. If not provided, we face the following error:

Image

@hurali97 hurali97 changed the title Bump to agp9 feat: adopt AGP v9 Jun 3, 2026
@hurali97 hurali97 marked this pull request as ready for review June 3, 2026 11:16
@facebook-github-tools facebook-github-tools Bot added the Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team. label Jun 3, 2026
Copy link
Copy Markdown
Contributor

@cortinico cortinico left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for taking a stab at this @hurali97 !

distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you send us a standalone PR with the Gradle bump only?

distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see comment above

Comment on lines +678 to +681
tasks.withType<JavaCompile>().configureEach {
exclude("com/facebook/react/processing/**")
exclude("com/facebook/react/module/processing/**")
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this? This should not be necessary at all

Comment thread gradle.properties
Comment on lines -7 to -12
# Those 2 properties are needed to make our project compatible with
# AGP 9.0.0 for the time being. Ideally we should not opt-out of
# builtInKotlin and newDsl once AGP 9.0.0 hits stable.
# More on this: https://developer.android.com/build/releases/agp-preview#android-gradle-plugin-built-in-kotlin
android.builtInKotlin=false
android.newDsl=false
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we good removing those?

api(libs.androidx.appcompat)
api(libs.androidx.appcompat.resources)
api(libs.androidx.autofill)
api(libs.androidx.collection)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a huge fan of this becoming an api dependency. Can this be an implementation dep instead?

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

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants