React Native Is New-Architecture-Only. Gradle Still Packed Someone Else's Code

The Android build failed with a message that was both precise and deeply unhelpful:
:app:mergeLibDexDebug
Type com.facebook.react.viewmanagers.RNCSafeAreaProviderManagerDelegate is defined multiple times
The library I was validating did not implement a safe-area view. It did not declare react-native-safe-area-context. It did not even expose a Codegen surface.
Yet its AAR contained RNCSafeAreaProviderManagerDelegate.
Apparently Gradle had packed someone else's luggage into my suitcase (and then charged me for having two copies).
The failure appeared in the consumer, not the library
React Native 0.82 was the first release that ran entirely on the New Architecture. The release announcement says that disabling the New Architecture is ignored from that version onward.
I previously wrote a broader dependency-readiness audit for this migration. This incident is the narrower artifact-level sequel: a library can compile in isolation and still poison a consuming app's dex merge.
In this case, the library applied the com.facebook.react Gradle plugin when New Architecture support was enabled:
if (isNewArchitectureEnabled()) {
apply plugin: "com.facebook.react"
}
There was no scoped react {} block. The plugin's Codegen scan walked the consuming workspace's node_modules, found packages that declared codegenConfig, and generated their manager delegate classes into this library's AAR.
The original package also shipped those classes. The app then received two definitions and failed during mergeLibDexDebug.
Why the usual checks looked healthy
The library's own source contained no duplicated class. Searching its Kotlin and Java directories found nothing suspicious.
The dependency graph was also legitimate. The consumer was allowed to depend on both libraries. Removing react-native-safe-area-context would have hidden the collision by removing one copy, not fixed the contaminated AAR.
The useful question was not “which dependency is duplicated?” It was “which artifact contains a class it does not own?”
Once the AAR became the evidence boundary, the failure made sense. Codegen had scanned a larger source tree than the library intended.
The fix was a smaller scan surface
The library had no Codegen specification in package.json, no *NativeComponent or *Spec files under src/, and no TurboModule classes under android/. Therefore, its correct generated output was empty.
The fix scoped the React Gradle plugin to the library's own JavaScript source:
react {
jsRootDir = file("../src/")
libraryName = "DaroM"
codegenJavaPackageName = "com.darom"
}
With jsRootDir restricted to src/, Codegen found nothing. That was success, not a missing-output bug.
This is an important testing pattern for tools that generate code: sometimes the expected assertion is that a directory or archive does not contain output for another package.
Verify ownership at the artifact boundary
A consumer build is the first necessary check because that is where duplicate definitions collide. It is not the only useful check.
For this class of failure, I want three pieces of evidence:
- The library source declares the intended Codegen surface, or explicitly declares none.
- The built AAR contains no foreign
*ManagerDelegateclasses. - A representative New Architecture consumer completes the Android build with the original dependencies present.
The third check prevents a tempting false fix: deleting the dependency that exposes the duplicate. The first two explain why the build now passes.
The readiness audit needs an artifact boundary
Library compatibility discussions often focus on TurboModules, Fabric components, and deprecated bridge APIs. Those are visible surfaces. Build-tool scan boundaries are easier to miss because they live one layer below the library's TypeScript and native implementation.
But New Architecture readiness also asks:
- Which directories does Codegen scan?
- Which generated classes enter the published artifact?
- Does the library generate only classes it owns?
- Does a real consumer resolve and package the same graph as an application?
The bug here was not an incompatible API. It was excessive reach.
If an Android dex merge says a generated React Native class is defined twice, do not start by excluding random dependencies. Open the AARs and find both owners. Then inspect the Codegen roots of the artifact that should never have contained the class.
A build tool that can see the whole workspace may decide the whole workspace belongs to it. Give it a smaller map.
Get the next post.
If you made it to the end, meet the next post in your inbox or RSS reader.