React-native: [Android] Error: Duplicate resources

Created on 10 Nov 2018  ·  103Comments  ·  Source: facebook/react-native

Environment

React Native Environment Info:

System:
  OS: macOS 10.14
  CPU: (4) x64 Intel(R) Core(TM) i5-7267U CPU @ 3.10GHz
  Memory: 103.10 MB / 8.00 GB
  Shell: 3.2.57 - /bin/bash
Binaries:
  Node: 8.12.0 - /usr/local/bin/node
  Yarn: 1.0.1 - /usr/local/bin/yarn
  npm: 6.4.1 - /usr/local/bin/npm
  Watchman: 4.7.0 - /usr/local/bin/watchman
SDKs:
  iOS SDK:
    Platforms: iOS 12.1, macOS 10.14, tvOS 12.1, watchOS 5.1
  Android SDK:
    API Levels: 16, 17, 19, 21, 23, 24, 25, 26, 27, 28
    Build Tools: 19.1.0, 20.0.0, 23.0.1, 23.0.2, 23.0.3, 25.0.0, 25.0.1, 25.0.2, 25.0.3, 26.0.0, 26.0.1, 26.0.2, 26.0.3, 27.0.0, 27.0.1, 27.0.3, 28.0.0, 28.0.0, 28.0.2, 28.0.3
    System Images: android-16 | ARM EABI v7a, android-16 | MIPS, android-16 | Intel x86 Atom, android-16 | Google APIs Intel x86 Atom, android-19 | Google APIs Intel x86 Atom, android-24 | Google Play Intel x86 Atom, android-26 | Google APIs Intel x86 Atom, android-26 | Google APIs Intel x86 Atom_64, android-26 | Google Play Intel x86 Atom, android-27 | Google Play Intel x86 Atom, android-28 | Google APIs Intel x86 Atom, android-P | Google APIs Intel x86 Atom, android-P | Google Play Intel x86 Atom
IDEs:
  Android Studio: 3.2 AI-181.5540.7.32.5056338
  Xcode: 10.1/10B61 - /usr/bin/xcodebuild
npmPackages:
  react: 16.6.0-alpha.8af6728 => 16.6.0-alpha.8af6728 
  react-native: 0.57.4 => 0.57.4 
npmGlobalPackages:
  babel-preset-react-native: 4.0.0
  react-native-cli: 2.0.1
  react-native-create-library: 3.1.2
  react-native-git-upgrade: 0.2.7

Description

I'm not able to create a release apk with the PNG image in Android. But can able to create a release apk when there is no PNG image in it. Here is the error I'm getting while generating the release build

[drawable-mdpi-v4/assets_mario] /Users/jeffreyrajan/Tutorials/RN/errorCheck/android/app/src/main/res/drawable-mdpi/assets_mario.png [drawable-mdpi-v4/assets_mario] /Users/jeffreyrajan/Tutorials/RN/errorCheck/android/app/build/generated/res/react/release/drawable-mdpi-v4/assets_mario.png: Error: Duplicate resources
:app:mergeReleaseResources FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':app:mergeReleaseResources'.
> [drawable-mdpi-v4/assets_mario] /Users/jeffreyrajan/Tutorials/RN/errorCheck/android/app/src/main/res/drawable-mdpi/assets_mario.png   [drawable-mdpi-v4/assets_mario] /Users/jeffreyrajan/Tutorials/RN/errorCheck/android/app/build/generated/res/react/release/drawable-mdpi-v4/assets_mario.png: Error: Duplicate resources

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.

* Get more help at https://help.gradle.org

Reproducible Demo

  1. Create a app - react-native init demo
  2. Create a assets folder in the project root folder.
  3. Add a PNG image inside the assets folder.
  4. Now implement a image component with the above PNG image.
  5. Now bundle it using the cmd
    react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest android/app/src/main/res/
  6. Then generate release apk using Generate Signed APK
Bug Android

Most helpful comment

Mapsy's answer should help https://stackoverflow.com/a/52750886
So basically you edit the /node_modules/react-native/react.gradle file
and add the doLast right after the doFirst block, manually.

doFirst { ... }
doLast {
    def moveFunc = { resSuffix ->
        File originalDir = file("$buildDir/generated/res/react/release/drawable-${resSuffix}");
        if (originalDir.exists()) {
            File destDir = file("$buildDir/../src/main/res/drawable-${resSuffix}");
            ant.move(file: originalDir, tofile: destDir);
        }
    }
    moveFunc.curry("ldpi").call()
    moveFunc.curry("mdpi").call()
    moveFunc.curry("hdpi").call()
    moveFunc.curry("xhdpi").call()
    moveFunc.curry("xxhdpi").call()
    moveFunc.curry("xxxhdpi").call()
}

All 103 comments

check this https://github.com/facebook/react-native/issues/19239#issuecomment-414564404

You need to remove drawable folder image if there is any?

@ZeroCool00 wont that affect the images in Android?

Mapsy's answer should help https://stackoverflow.com/a/52750886
So basically you edit the /node_modules/react-native/react.gradle file
and add the doLast right after the doFirst block, manually.

doFirst { ... }
doLast {
    def moveFunc = { resSuffix ->
        File originalDir = file("$buildDir/generated/res/react/release/drawable-${resSuffix}");
        if (originalDir.exists()) {
            File destDir = file("$buildDir/../src/main/res/drawable-${resSuffix}");
            ant.move(file: originalDir, tofile: destDir);
        }
    }
    moveFunc.curry("ldpi").call()
    moveFunc.curry("mdpi").call()
    moveFunc.curry("hdpi").call()
    moveFunc.curry("xhdpi").call()
    moveFunc.curry("xxhdpi").call()
    moveFunc.curry("xxxhdpi").call()
}

@ZeroCool00 @mkchx I checked with both of your answer, its working. Thanks a lot guys :)

Hi all how will we able to get this done with jenkins job. As it will do npm install always which override this change in react.gradle file. We can create build on android studio for android but not possible on jenkins.

Hi @vivek-walecha-657 I haven't tried this but you can try this command for creating offline bundling

react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/app/src/main/assets/index.android.bundle

@jeffreyrajanofficial Thanx for writing, the solution you have provided will help if we go and change react.gradle file. But I don't want to make the release by changing the react.gradle file every time i do npm install everywhere.

@jeffreyrajanofficial Can you please tell which version(latest lower than this or higher than this) is working fine, without this issue. Because release notes don't tell anything that this issue is resolved.

Things are sorted now in RN > 57 react.gradle file automatically creates the bundle.
for creating a release build you dont need to run the npm run build:android:release

Using 55.4 react native version here is my sample project gist for build.gradle package.json with fixes.

https://gist.github.com/Abhishekgarg727/daf031fb9f94fdfd985e84db57dedbe1

I was still seeing this, using macOS 10.14.3 + RN 0.57.8 + Android Studio 3.3 + Gradle 4.10.3. Maybe I'm not the only one? Or maybe someone here can confirm it works so I'll dig more and fix it for myself for real.

I'm currently working around it with the "patch-package" package in combination with the attached patch based on the above comment from @mkchx (with .txt suffix appended so github would accept the attachment) in order to automagically fix it on 'npm install' after adding postinstall: patch-package to my package.json scripts.

Maybe this is useful to someone...
react-native+0.57.8.patch.txt

Remove the files you might have on:

android/app/src/main/res/drawable-mdpi/
android/app/src/main/res/drawable-xhdpi/
android/app/src/main/res/drawable-xxhdpi/
Run Build again, This fixed the issue for me.

I was still seeing this in RN0.58.x and it continues in RN0.59.x - are we doing something wrong here or is this really a bug?

I continue to have success with the workaround from @mkchx encoded in patch form in the patches directory for use with the patch-package module and this patch (updated for RN0.59.1)

react-native+0.59.1.patch.txt

If you have extra resources added in custom folders, you might want to try something like this:

doLast {
    def moveFunc = { resSuffix ->
        File originalDir = file("$buildDir/generated/res/react/release/${resSuffix}");
        if (originalDir.exists()) {
            File destDir = file("$buildDir/../src/main/res/${resSuffix}");
            ant.move(file: originalDir, tofile: destDir);
        }
    }
    moveFunc.curry("drawable-ldpi").call()
    moveFunc.curry("drawable-mdpi").call()
    moveFunc.curry("drawable-hdpi").call()
    moveFunc.curry("drawable-xhdpi").call()
    moveFunc.curry("drawable-xxhdpi").call()
    moveFunc.curry("drawable-xxxhdpi").call()
    moveFunc.curry("raw").call()
}

But if you have dependencies that are packing their own assets, it's not working, still getting this error (edited for clarity):

Execution failed for task ':app:mergeReleaseResources'.

> [drawable-xxxhdpi-v4/node_modules_reactnavigationstack_dist_views_assets_backicon] 
/[...]/android/app/src/main/res/drawable-xxxhdpi/node_modules_reactnavigationstack_dist_views_assets_backicon.png

[drawable-xxxhdpi-v4/node_modules_reactnavigationstack_dist_views_assets_backicon] 
/[...]/android/app/build/generated/res/react/release/drawable-xxxhdpi/node_modules_reactnavigationstack_dist_views_assets_backicon.png: 

Error: Duplicate resources

Is this actively assessed, or should we move forward with our own patches?

@dragosroua I see a missing hyphen in your xxxhdpi curry. Coincidentally the same leading paths with problems for you?

You beat me by 2 minutes, I was about to edit that part. Yes, everything bundles ok now, but the bit with "raw" path for custom resources might be useful for somebody.

@dragosroua glad you're compiling now - I remember how frustrating this one was for me, and I'm still amazed it's not fixed in master though I haven't proposed a PR either so I guess I get out what I put in...

I was still seeing this, using macOS 10.14.3 + RN 0.57.8 + Android Studio 3.3 + Gradle 4.10.3. Maybe I'm not the only one? Or maybe someone here can confirm it works so I'll dig more and fix it for myself for real.

I'm currently working around it with the "patch-package" package in combination with the attached patch based on the above comment from @mkchx (with .txt suffix appended so github would accept the attachment) in order to automagically fix it on 'npm install' after adding postinstall: patch-package to my package.json scripts.

Maybe this is useful to someone...
react-native+0.57.8.patch.txt

Pls explain me why for my react-native 0.57.5 it's doesn't work?
I created pacth file. Added to package.json. Run npm install and has as result

        def currentBundleTask = tasks.create(
            name: "bundle${targetName}JsAndAssets",
            type: Exec) {
            group = "react"
            description = "bundle JS and assets for ${targetName}."

            // Create dirs if they are not there (e.g. the "clean" task just ran)
            doFirst {
                jsBundleDir.deleteDir()
                jsBundleDir.mkdirs()
                resourcesDir.deleteDir()
                resourcesDir.mkdirs()
            }

            // Set up inputs and outputs so gradle can cache the result
            inputs.files fileTree(dir: reactRoot, excludes: inputExcludes)
            outputs.dir jsBundleDir
            outputs.dir resourcesDir

without necessary changes.

@zakabluk you'd need to post the output of your npm install, but as a guess it is because the patch-package package is very careful about version numbers. you are trying on 57.5, but the patch is against 57.8?

I usually make a python scripts for patching node_modules.
Add this as postinstall.py and add it to your postinstall script or run it with ./postinstall.py

#!/usr/bin/env python3

import os
import textwrap

def file_dir():
  return os.path.dirname(os.path.realpath(__file__))

def read_file(filename):
    '''
    Reads the specified file.

    :param filename: The file to read
    :return: The content of the specified file
    '''
    if os.path.exists(filename):
        with open(filename, "r") as file:
            return file.read()
    else:
        raise IOError("file {} not found.".format(filename))

def write_file(filename, text):
    '''
    Writes the specified text to the specified file.

    :param filename: The file to write to
    :param text: The text to write
    '''
    with open(filename, "w") as file:
        file.write(text)

def fix_android_assets():
  print("Fixing android error with duplicate assets: https://github.com/facebook/react-native/issues/22234")

  gradle_file_path = "{}/node_modules/react-native/react.gradle".format(file_dir())

  code_snippet = textwrap.indent("""\
            // Added by post_install
            // Fix for: https://github.com/facebook/react-native/issues/22234
            doLast {
                def moveFunc = { resSuffix ->
                    File originalDir = file("$buildDir/generated/res/react/release/drawable-${resSuffix}");
                    if (originalDir.exists()) {
                        File destDir = file("$buildDir/../src/main/res/drawable-${resSuffix}");
                        ant.move(file: originalDir, tofile: destDir);
                    }
                }
                moveFunc.curry("ldpi").call()
                moveFunc.curry("mdpi").call()
                moveFunc.curry("hdpi").call()
                moveFunc.curry("xhdpi").call()
                moveFunc.curry("xxhdpi").call()
                moveFunc.curry("xxxhdpi").call()
            }
  """, "")

  text = read_file(gradle_file_path)

  start = text.find("doFirst", 0)
  end = text.find("}", start)
  end = text.find("\n", end) + 1

  text = text[:end] + code_snippet + text[end:]

  write_file(gradle_file_path, text)

def main():
    fix_android_assets()

if __name__ == "__main__":
    main()

Here you are able to add your own scripts if required

Looks like a re-implementation of what you get with npm install patch-package but if python is your thing and you want to maintain more code yourself it does seem viable. I'm still using patch-package for what it's worth, with 0.59.3 like so
react-native+0.59.3.patch.txt

@hramos - #19239 was similar (I think) and this is long-standing but appears to have a fix. Does this just need a PR for an ultimate fix or am I missing a reason why the patch used here is not viable? (I might be). If we just need a PR I could send one in...

Looks like a re-implementation of what you get with npm install patch-package but if python is your thing and you want to maintain more code yourself it does seem viable. I'm still using patch-package for what it's worth, with 0.59.3 like so
react-native+0.59.3.patch.txt

@hramos - #19239 was similar (I think) and this is long-standing but appears to have a fix. Does this just need a PR for an ultimate fix or am I missing a reason why the patch used here is not viable? (I might be). If we just need a PR I could send one in...

How to use this path , thank

@ZhanRu - https://github.com/ds300/patch-package#set-up - you just want to put that patch (with .patch extension) into the 'patches' directory in your project after installing and setting up patch-package

@ZhanRu - https://github.com/ds300/patch-package#set-up - you just want to put that patch (with .patch extension) into the 'patches' directory in your project after installing and setting up patch-package

Thank you very much

For anyone still following along, I recently integrated an external system and needed to separate my testing from production external data, which leads to using "flavors" in gradle so you can have qaDebug, stagingRelease, etc etc pointing to different external system. The patch here did not support that though, so I added flavor support, and my patch looks like this now. It lives in patches/react-native+0.59.5.patch where it is applied during npm i runs after npm install patch-package

diff --git a/node_modules/react-native/react.gradle b/node_modules/react-native/react.gradle
index 4ead2b6..e0f92b7 100644
--- a/node_modules/react-native/react.gradle
+++ b/node_modules/react-native/react.gradle
@@ -48,6 +48,33 @@ afterEvaluate {
                 resourcesDir.mkdirs()
             }

+            // From https://stackoverflow.com/questions/53239705/react-native-error-duplicate-resources-android
+            // Currently has no solution?
+
+            // IF you are using flavors, add flavor name to the path you move from
+            def flavorPathSegment = ""
+            android.productFlavors.all { flavor ->
+                if (targetName.toLowerCase().contains(flavor.name)) {
+                    flavorPathSegment = flavor.name + "/"
+                }
+            }
+
+            doLast {
+                def moveFunc = { resSuffix ->
+                    File originalDir = file("$buildDir/generated/res/react/${flavorPathSegment}release/drawable-${resSuffix}")
+                    if (originalDir.exists()) {
+                        File destDir = file("$buildDir/../src/main/res/drawable-${resSuffix}")
+                        ant.move(file: originalDir, tofile: destDir);
+                    }
+                }
+                moveFunc.curry("ldpi").call()
+                moveFunc.curry("mdpi").call()
+                moveFunc.curry("hdpi").call()
+                moveFunc.curry("xhdpi").call()
+                moveFunc.curry("xxhdpi").call()
+                moveFunc.curry("xxxhdpi").call()
+            }
+
             // Set up inputs and outputs so gradle can cache the result
             inputs.files fileTree(dir: reactRoot, excludes: inputExcludes)
             outputs.dir(jsBundleDir)

In my case problem persists with raw directory.

Version: react-native 0.59.5

My solution:

doLast {                                                                                            
  def moveFunc = { resSuffix ->                                                                   
    File originalDir = file("$buildDir/generated/res/react/release/drawable-${resSuffix}");     
    if (originalDir.exists()) {                                                                 
      File destDir = file("$buildDir/../src/main/res/drawable-${resSuffix}");                 
      ant.move(file: originalDir, tofile: destDir);                                           
    }
  } 
  def moveRawFunc = { dir ->                                                                   
    File originalDir = file("$buildDir/generated/res/react/release/${dir}");     
    if (originalDir.exists()) {                                                                 
      File destDir = file("$buildDir/../src/main/res/${dir}");                 
      ant.move(file: originalDir, tofile: destDir);                                           
    }
  }  
  moveFunc.curry("ldpi").call()
  moveFunc.curry("mdpi").call()
  moveFunc.curry("hdpi").call()
  moveFunc.curry("xhdpi").call()
  moveFunc.curry("xxhdpi").call()
  moveFunc.curry("xxxhdpi").call()
  moveRawFunc.curry("raw").call()
}

Regards

@Dbroqua your solution worked for me (version react-native 0.59.5). Thank you.

Very interesting - I haven't had a problem with raw - do be aware that my latest version of the patch added support for flavors. If you start doing flavors you will want that flavor-support now in both of those functions. Maybe it could be parameterized somehow so the 2 funcs aren't as repetitive but I'm not good enough at groovy to contemplate it.

I can only imagine this isn't an issue in facebook and on react-native CI because they are using BUCK and their CI builds clean every time. Does anyone have a clean reproduction of this so we can get a fix upstream?

In my case the raw directory contains some mp3 used in my application.

That might form the basis of a quick+easy repro then. I don't have assets like that (yet) but easy enough to react-native init for a repo, put some assets in then I think on the second release build (maybe even the first?) you are hosed...

@Dbroqua just a heads up, my patch was merged but your mention of raw directories being very closely related but not included in my patch is also reflected in the merge. Now that my patch is in, you may want to see what change is required for the raw directory and propose a PR with a continuation of the fix, extending it for your case?

Ok,

I will do the necessary ASAP.

Regards,
Damien

Fixed!

In my case there were files in the drawables res/drawable-* directories that were lingering from some other dev on my teams commit - I was getting a "Error: Duplicate resources" pointing at those file names - I deleted the files from drawables and everything works fine 👍

Mapsy's answer should help https://stackoverflow.com/a/52750886
So basically you edit the /node_modules/react-native/react.gradle file
and add the doLast right after the doFirst block, manually.

doFirst { ... }
doLast {
    def moveFunc = { resSuffix ->
        File originalDir = file("$buildDir/generated/res/react/release/drawable-${resSuffix}");
        if (originalDir.exists()) {
            File destDir = file("$buildDir/../src/main/res/drawable-${resSuffix}");
            ant.move(file: originalDir, tofile: destDir);
        }
    }
    moveFunc.curry("ldpi").call()
    moveFunc.curry("mdpi").call()
    moveFunc.curry("hdpi").call()
    moveFunc.curry("xhdpi").call()
    moveFunc.curry("xxhdpi").call()
    moveFunc.curry("xxxhdpi").call()
}

not working for me.

@Nextt1 you'll need to open a new issue - with a reproducible test case in a public repo if at all possible - and perhaps propose a new PR. This issue was closed by my PR and then an additional one (handling a case my first PR did not handle well) was also merged, so we've all moved on and I think it's working for most so this issue is not likely to get attention.

Hey everyone - the PR I proposed to fix this was merged but apparently causes a regression - https://github.com/facebook/react-native/issues/25325 - I'm investigating but if there are any gradle gurus here that now how to fix this issue without causing that regression, help would be appreciated - thanks!

Okay, the related PR here is going to have a "revert PR" - it causes a regression, and the underlying issue that caused this problem was bad documentation really.

Here's the thing: you should never copy things into the src directory during a build really. You need to copy things into intermediates and generated etc. If you have already copied things into src (from previous builds using this patch, or from a react-native bundle command): you need to clear those out so your src/main/res directory is clean - only real assets from your project

Now, to build an APK with an offline bundle - even in dev so you can run it on API < 17 you should do things differently than everyone on the web recommends (or you'll have this problem).

What you want is this in your android/app/build.gradle:

project.ext.react = [

        // This is what most people will need
        bundleInDebug: project.hasProperty("bundleInDebug") ? project.getProperty("bundleInDebug") : false,

        // If you use build variants it has to be like this - put your own names in there
        bundleInDevDebug: project.hasProperty("bundleInDevDebug") ? project.getProperty("bundleInDevDebug") : false,
        bundleInQaDebug: project.hasProperty("bundleInQaDebug") ? project.getProperty("bundleInQaDebug") : false,
        bundleInStagingDebug: project.hasProperty("bundleInStagingDebug") ? project.getProperty("bundleInStagingDebug") : false,
        bundleInProdDebug: project.hasProperty("bundleInProdDebug") ? project.getProperty("bundleInProdDebug") : false
]

Then you call react-native something like this - sending a gradle project property through via an environment variable:
ORG_GRADLE_PROJECT_bundleInDebug=true npx react-native run-android

(or for variants something like this ORG_GRADLE_PROJECT_bundleInDevDebug=true npx react-native run-android --variant devDebug)

I was still seeing this, using macOS 10.14.3 + RN 0.57.8 + Android Studio 3.3 + Gradle 4.10.3. Maybe I'm not the only one? Or maybe someone here can confirm it works so I'll dig more and fix it for myself for real.

I'm currently working around it with the "patch-package" package in combination with the attached patch based on the above comment from @mkchx (with .txt suffix appended so github would accept the attachment) in order to automagically fix it on 'npm install' after adding postinstall: patch-package to my package.json scripts.

Maybe this is useful to someone...
react-native+0.57.8.patch.txt

Awesome! For now, it's the best answer!

I'm still seeing this in 0.59.9. @mkchx 's answer sorted out the issue for me

@juliancorrea @scgough just be careful with that, I followed that solution style to its full end with a PR accepted even, but after follow-on problems we needed to revert it. It's a dead-end solution. I posted full details of the currently recommended solution a couple comments above and can only recommend the new style. If you use my old attached patch based on @mkchx answer it will work for you, for now, but only under certain conditions - it fails in other common scenarios that your project may need in the future

@mikehardy OK - I will look at your solution now.
I _think_ what I actually want is for Android Studio's signed APK build to skip the bundle (as I manually do this via terminal).
I will have a look at the settings you've supplied and see if they help.

Im still having trouble with this...
I can't seem to create my release APK file. I get the following error for every asset file:

[drawable-mdpi-v4/filename] /Users/me/React/myapp/android/app/src/main/res/drawable-mdpi/filename.png
[drawable-mdpi-v4/filename] /Users/me/React/myapp/android/app/build/generated/res/react/release/drawable-mdpi/filename.png: Error: Duplicate resources

I have deleted both instances of drawable-mdpi folders but the APK generation places them back and errors.
I am running the following manually to create my bundle:

react-native bundle --platform android --dev false --entry-file index.android.js --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest android/app/src/main/res/

In my app gradle I have:

project.ext.react = [
    entryFile: "index.android.js",
    bundleInRelease: true    //I've tried true and false here
]

apply from: "../../node_modules/react-native/react.gradle"

Just as extra info - my app builds fine in debug (via react-native run-android)
At the moment the only way I can get my release APK to build is via the doLast reverted PR fix above.

I think I might have the reason/solution for my issue at least.

My old bundle command (I've used since RN 0.2.x?!) pushed the assets to the destination folder android/app/src/main/res/

I noted a post on the link below which states that > RN 57 actually needs assets pushed to the following folder:
android/app/build/intermediates/res/merged/release/

Source: https://github.com/facebook/react-native/issues/19211#issuecomment-448301870

I did the following:

  • deleted the drawable-mdpi assets that had been copied in to android/app/src/main/res/
  • updated my bundle command to use the new folder location
  • my APK now builds...(_I'm about to test it_)

update
The APK installed but the app crashes now with the exception:
com.facebook.react.bridge.JSApplicationIllegalArgumentException: Error while updating property 'defaultSrc' of a view managed by: RCTImageView

...so it looks like it can't find the images 😞

update 2
The only way I've been able to get the APK to work (so far) is to do the following:

  • revert my bundle call to use the following for assets: android/app/src/main/res/
  • run the bundle command
  • rename the android/app/src/main/res/drawable-mdpi to android/app/src/main/res/drawable-hdpi
  • build the signed APK

my build gradle is:

project.ext.react = [
    entryFile: "index.android.js"
]

The APK then builds and runs...

Side Note:
I did get an unexpected bug crop up resulting in a createBitmap OutOfMemory exception on a image slider component.
I've added the following to my manifest and the app runs again: android:largeHeap="true"
I'm aware that addition is bypassing what could be a leak/component issue but so far the app runs without issue again.

In React Native 0.60.0 I've noticed that native base is also creating duplicate resources but the above workaround will not work because it's targeting raw folder.

[raw/node_modules_nativebase_dist_src_basic_icon_nbicons] C:\Projects\some-app\CLIENT\android\app\src\main\resrawnode_modules_nativebase_dist_src_basic_icon_nbicons.json
[raw/node_modules_nativebase_dist_src_basic_icon_nbicons] C:\Projects\some-app\CLIENT\android\app\build\generated\res\reactreleaserawnode_modules_nativebase_dist_src_basic_icon_nbicons.json: Error: Duplicate resources

For other users having this issue:

        doLast {
            def moveFunc = { resSuffix ->
                File originalDir = file("$buildDir/generated/res/react/release/drawable-${resSuffix}");
                if (originalDir.exists()) {
                    File destDir = file("$buildDir/../src/main/res/drawable-${resSuffix}");
                    ant.move(file: originalDir, tofile: destDir);
                }
            }

            moveFunc.curry("ldpi").call()
            moveFunc.curry("mdpi").call()
            moveFunc.curry("hdpi").call()
            moveFunc.curry("xhdpi").call()
            moveFunc.curry("xxhdpi").call()
            moveFunc.curry("xxxhdpi").call()

            File originalDir = file("$buildDir/generated/res/react/release/raw");
                if (originalDir.exists()) {
                    File destDir = file("$buildDir/../src/main/res/raw");
                    ant.move(file: originalDir, tofile: destDir);
            }
        }

how can we solve this if we are using CI?

In my case problem persists with raw directory.

Version: react-native 0.59.5

My solution:

doLast {                                                                                            
  def moveFunc = { resSuffix ->                                                                   
    File originalDir = file("$buildDir/generated/res/react/release/drawable-${resSuffix}");     
    if (originalDir.exists()) {                                                                 
      File destDir = file("$buildDir/../src/main/res/drawable-${resSuffix}");                 
      ant.move(file: originalDir, tofile: destDir);                                           
    }
  } 
  def moveRawFunc = { dir ->                                                                   
    File originalDir = file("$buildDir/generated/res/react/release/${dir}");     
    if (originalDir.exists()) {                                                                 
      File destDir = file("$buildDir/../src/main/res/${dir}");                 
      ant.move(file: originalDir, tofile: destDir);                                           
    }
  }  
  moveFunc.curry("ldpi").call()
  moveFunc.curry("mdpi").call()
  moveFunc.curry("hdpi").call()
  moveFunc.curry("xhdpi").call()
  moveFunc.curry("xxhdpi").call()
  moveFunc.curry("xxxhdpi").call()
  moveRawFunc.curry("raw").call()
}

Regards

This works!

[drawable-mdpi-v4/node_modules_reactnativemaplink_src_images_uber] /Users/umair/my-app/android/app/src/main/res/drawable-mdpi/node_modules_reactnativemaplink_src_images_uber.png   [drawable-mdpi-v4/node_modules_reactnativemaplink_src_images_uber] /Users/umair/my-app/android/app/build/generated/res/react/debug/drawable-mdpi/node_modules_reactnativemaplink_src_images_uber.png: Error: Duplicate resources

I get this error when I run react-native run-android but not when I build and run the release build. None of the above solutions work for me.

RN 0.60.5

UPDATE: I realized I should modify the above "patch" methods to cite the debug instead of release directories and that fixed it. I too had to fix my raw directory

I am also having same issue and react.gradle patch won't fix it. In my case my duplicated resources are .OBJ and .MTL files, don't have any problems with image resources.

[raw/assets_res_salad_salad] /Users/sercanov/Projects/ARt/diner/DinerApp/android/app/src/main/res/raw/assets_res_salad_salad.mtl [raw/assets_res_salad_salad] /Users/sercanov/Projects/ARt/diner/DinerApp/android/app/src/main/res/raw/assets_res_salad_salad.obj: Error: Duplicate resources
[raw/assets_res_steak_steak] /Users/sercanov/Projects/ARt/diner/DinerApp/android/app/src/main/res/raw/assets_res_steak_steak.mtl [raw/assets_res_steak_steak] /Users/sercanov/Projects/ARt/diner/DinerApp/android/app/src/main/res/raw/assets_res_steak_steak.obj: Error: Duplicate resources
[raw/assets_res_salmon_salmon] /Users/sercanov/Projects/ARt/diner/DinerApp/android/app/src/main/res/raw/assets_res_salmon_salmon.mtl [raw/assets_res_salmon_salmon] /Users/sercanov/Projects/ARt/diner/DinerApp/android/app/src/main/res/raw/assets_res_salmon_salmon.obj: Error: Duplicate resources

@sercanov are you still trying to use the copy to src? That style was investigated and fails for known reasons, there are supported ways to build that don't place things in src - or are you trying those not-copy-to-src ways and still failing? https://github.com/facebook/react-native/issues/22234#issuecomment-504721069

hey @mikehardy actually tried both approaches, neither of them made it work in release builds. It works okay in debug mode btw.

When using not-copy-to-src way ORG_GRADLE_PROJECT_bundleInArRelease=true npx react-native run-android --variant arRelease I am getting this one;

Execution failed for task ':app:mergeArReleaseResources'.
[raw/assets_res_salad_salad] /Users/sercanov/Projects/ARt/diner/DinerApp/android/app/build/generated/res/react/ar/release/raw/assets_res_salad_salad.mtl [raw/assets_res_salad_salad] /Users/sercanov/Projects/ARt/diner/DinerApp/android/app/build/generated/res/react/ar/release/raw/assets_res_salad_salad.obj: Error: Duplicate resources
[raw/assets_res_steak_steak] /Users/sercanov/Projects/ARt/diner/DinerApp/android/app/build/generated/res/react/ar/release/raw/assets_res_steak_steak.mtl [raw/assets_res_steak_steak] /Users/sercanov/Projects/ARt/diner/DinerApp/android/app/build/generated/res/react/ar/release/raw/assets_res_steak_steak.obj: Error: Duplicate resources
[raw/assets_res_salmon_salmon] /Users/sercanov/Projects/ARt/diner/DinerApp/android/app/build/generated/res/react/ar/release/raw/assets_res_salmon_salmon.mtl [raw/assets_res_salmon_salmon] /Users/sercanov/Projects/ARt/diner/DinerApp/android/app/build/generated/res/react/ar/release/raw/assets_res_salmon_salmon.obj: Error: Duplicate resources

When used copy-to-src doLast way; I had to clear android/build folder every time I build to make it succeed but somehow assets weren't available in app. This may be related to my code, currently investigating about it.

When switching to the not-copy-to-src way you have to clean everything out first, to make the switch. My project had lots of "litter" from a bundling perspective from the previous copy-to-src style that I had to clean out before it worked, then it worked every time.

@rahulkumar1409 this helped me in this error but it throws another error

Task :react-native-simple-download-manager:verifyReleaseResources FAILED

Do you have any idea why this happen ??

This solution worked for me.
So basically you edit the /node_modules/react-native/react.gradle file
and add the doLast right after the doFirst block, manually.

doFirst { ... }
doLast {
def moveFunc = { resSuffix ->
File originalDir = file("$buildDir/generated/res/react/release/drawable-${resSuffix}");
if (originalDir.exists()) {
File destDir = file("$buildDir/../src/main/res/drawable-${resSuffix}");
ant.move(file: originalDir, tofile: destDir);
}
}
moveFunc.curry("ldpi").call()
moveFunc.curry("mdpi").call()
moveFunc.curry("hdpi").call()
moveFunc.curry("xhdpi").call()
moveFunc.curry("xxhdpi").call()
moveFunc.curry("xxxhdpi").call()
}

We are on react-native 0.60.5.

We're using product flavors just like @mikehardy mentioned and we had problems with the 'raw' folder just like @Dbroqua experienced.

I ended up mixing both solutions and we use this code:

doLast {
    def moveFunc = { resSuffix ->
        File originalDir = file("$buildDir/generated/res/react/${flavorPathSegment}release/drawable-${resSuffix}")
        if (originalDir.exists()) {
            File destDir = file("$buildDir/../src/main/res/drawable-${resSuffix}")
            ant.move(file: originalDir, tofile: destDir)
        }
    }
    def moveRawFunc = { dir ->
        File originalDir = file("$buildDir/generated/res/react/${flavorPathSegment}release/${dir}")
        if (originalDir.exists()) {
            File destDir = file("$buildDir/../src/main/res/${dir}")
            ant.move(file: originalDir, tofile: destDir)
        }
    }

    moveFunc.curry("ldpi").call()
    moveFunc.curry("mdpi").call()
    moveFunc.curry("hdpi").call()
    moveFunc.curry("xhdpi").call()
    moveFunc.curry("xxhdpi").call()
    moveFunc.curry("xxxhdpi").call()
    moveRawFunc.curry("raw").call()
}

This thread has been really helpful, thanks to everyone for contributing. 😄

I am getting the duplicate error only on release builds. I'm using react-native 60.5

/release/drawable-xhdpi/node_modules_reactnavigationstack_lib_module_views_assets_backicon.png: Error: Duplicate resources

I've tried setting bundleInDebug to true and false but with no affect.

I'm building using app center, this requires an src/main/assets/appcenter-config.json file. I also have a src/main/assets/fonts directory as I'm using custom fonts. Is this likely to be causing the problem?

@ARichIVC I'm guessing the problem is the file referenced in your error message.

It looks like the standard "my src/ directory is polluted from previous copy-style attempts to handle bundle packaging" problem from this original solution before we all realized that it's a dead-end solution and you need to clear src/ out of those copied things before trying the follow-on solution of doing the bundleInDebug stuff. It's all in this thread which is unfortunately unwieldy and long, but the info is in there

Thanks @mikehardy for your help . I found it was fine to leave in the fonts etc in the assets directory but did need to delete the other bits and pieces hanging around. Namely everything in \android\app\src\main\res\drawable-hdpi\

Note that even for fonts they should be in the right directory - an example: https://github.com/oblador/react-native-vector-icons#android which directs you to call this file which carefully copies into a non-src directory https://github.com/oblador/react-native-vector-icons/blob/master/fonts.gradle#L16 - everyone's project is different of course, but I thought it was worth mentioning

We are on react-native 0.60.5.

We're using product flavors just like @mikehardy mentioned and we had problems with the 'raw' folder just like @Dbroqua experienced.

I ended up mixing both solutions and we use this code:

doLast {
    def moveFunc = { resSuffix ->
        File originalDir = file("$buildDir/generated/res/react/${flavorPathSegment}release/drawable-${resSuffix}")
        if (originalDir.exists()) {
            File destDir = file("$buildDir/../src/main/res/drawable-${resSuffix}")
            ant.move(file: originalDir, tofile: destDir)
        }
    }
    def moveRawFunc = { dir ->
        File originalDir = file("$buildDir/generated/res/react/${flavorPathSegment}release/${dir}")
        if (originalDir.exists()) {
            File destDir = file("$buildDir/../src/main/res/${dir}")
            ant.move(file: originalDir, tofile: destDir)
        }
    }

    moveFunc.curry("ldpi").call()
    moveFunc.curry("mdpi").call()
    moveFunc.curry("hdpi").call()
    moveFunc.curry("xhdpi").call()
    moveFunc.curry("xxhdpi").call()
    moveFunc.curry("xxxhdpi").call()
    moveRawFunc.curry("raw").call()
}

This thread has been really helpful, thanks to everyone for contributing. 😄

it works! thank a lot! 👍

For react native 0.59.1 or greater you need to add below code in react.gradle in node_modules/react-native.

for flavoured use below code.

        def flavorPathSegment = ""
        android.productFlavors.all { flavor ->
            if (targetName.toLowerCase().contains(flavor.name)) {
                flavorPathSegment = flavor.name
           }
        }
        doLast {
            def moveFunc = { resSuffix ->
                File originalDir = file("$buildDir/generated/res/react/${flavorPathSegment}/release/drawable-${resSuffix}");
                if (originalDir.exists()) {
                    File destDir = file("$buildDir/../src/main/res/drawable-${resSuffix}");
                    ant.move(file: originalDir, tofile: destDir);
                }
            }
            moveFunc.curry("ldpi").call()
            moveFunc.curry("mdpi").call()
            moveFunc.curry("hdpi").call()
            moveFunc.curry("xhdpi").call()
            moveFunc.curry("xxhdpi").call()
            moveFunc.curry("xxxhdpi").call()
        }

@dayachand-systematix this bad advice. That style solution was pursued to the point where it had a PR and was merged even. Then it was discovered it was the wrong direction and the PR was reverted. Would you advise people use a solution that actually caused a PR merge to be reverted? I would not. https://github.com/facebook/react-native/issues/22234#issuecomment-504721069

@mikehardy Yes I know this is not permanent solution but It is working fine. And also I can't able to use your solution because I have some some other files like splash screens and DB in res directory.

Yes @dayachand-systematix me too:

mike@isabela:~/work/Kullki/ksocialscore/packages/public-app/android/app (lerna-import) % find . -type f |grep res
./src/main/res/drawable-xhdpi/minilogo_bw.png
./src/main/res/mipmap-xxhdpi/ic_launcher.png
./src/main/res/values-v21/styles.xml
./src/main/res/drawable-xxhdpi/minilogo_bw.png
./src/main/res/drawable-xxhdpi/kscore_splash.png
./src/main/res/drawable-hdpi/minilogo_bw.png
./src/main/res/mipmap-xxxhdpi/ic_launcher.png
./src/main/res/mipmap-hdpi/ic_launcher.png
./src/main/res/layout/launch_screen.xml
./src/main/res/drawable/background_launch.xml
./src/main/res/values/colors.xml
./src/main/res/values/styles.xml
./src/main/res/values/strings.xml
./src/main/res/mipmap-xhdpi/ic_launcher.png
./src/main/res/xml/filepaths.xml
./src/main/res/xml/react_native_config.xml
./src/main/res/drawable-mdpi/minilogo_bw.png
./src/main/res/drawable-xxxhdpi/minilogo_bw.png
./src/main/res/mipmap-mdpi/ic_launcher.png
./src/qa/res/mipmap-xxhdpi/ic_launcher.png
./src/qa/res/mipmap-xxxhdpi/ic_launcher.png
./src/qa/res/mipmap-hdpi/ic_launcher.png
./src/qa/res/values/strings.xml
./src/qa/res/mipmap-xhdpi/ic_launcher.png
./src/qa/res/mipmap-mdpi/ic_launcher.png
./src/debug/res/xml/react_native_config.xml
./src/staging/res/mipmap-xxhdpi/ic_launcher.png
./src/staging/res/mipmap-xxxhdpi/ic_launcher.png
./src/staging/res/mipmap-hdpi/ic_launcher.png
./src/staging/res/values/strings.xml
./src/staging/res/mipmap-xhdpi/ic_launcher.png
./src/staging/res/mipmap-mdpi/ic_launcher.png
./src/dev/res/mipmap-xxhdpi/ic_launcher.png
./src/dev/res/mipmap-xxxhdpi/ic_launcher.png
./src/dev/res/mipmap-hdpi/ic_launcher.png
./src/dev/res/values/strings.xml
./src/dev/res/mipmap-xhdpi/ic_launcher.png
./src/dev/res/mipmap-mdpi/ic_launcher.png

And still the suggested / not-PR-reverted solution works for me :man_shrugging:

Heya, we fixed it by removing the --asset-dest parameter:

react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/app/src/main/assets/index.android.bundle

@hery-finimize hi - wouldn't removing the --asset-dest parameter result in the latest assets not being bundled to the correct place...so when you create your APK there would be a danger of having missing/out of date asset files in the app?

@scgough I figured that might be the case. If so, we can selectively remove all the duplicate assets except for the local assets we need before building with Android Studio or whichever production workflow, e.g.

find android/app/src/main/res/drawable-* \( -name launch_screen.png -o -name ic_stat_onesignal_default.png -prune \) -o -type f -exec rm {} +

Not ideal but it should work until this is resolved.

This issue caused when if you have files same name and different extension in assets folder like that

back.png
back.jpg

because in android both of them are calling like that;

R.drawable.back
R.drawable.back

remove or rename one of the file.

Hi,
I've tried to modify react.gradle : The build goes one step further (the app.aab is build but I can't build the APK.

I've tried to build the APK with Android Studio and there was no problem (with the patch to react.gradle).
It's quite strange for me because the process to build the APK should be the same in both cases. Am I wrong ?

This comment worked for 0.59.10. Mixed with this gist.

Thanks a lot, @dayachand-systematix !

What worked for me was a combination of two comments:

I'm pasting this here for anyone who else might need it.

```
def flavorPathSegment = ""
android.productFlavors.all { flavor ->
if (targetName.toLowerCase().contains(flavor.name)) {
flavorPathSegment = flavor.name
}
}

        doLast {
            def moveFunc = { resSuffix ->
                File originalDir = file("$buildDir/generated/res/react/${flavorPathSegment}release/drawable-${resSuffix}")
                if (originalDir.exists()) {
                    File destDir = file("$buildDir/../src/main/res/drawable-${resSuffix}")
                    ant.move(file: originalDir, tofile: destDir)
                }
            }
            def moveRawFunc = { dir ->
                File originalDir = file("$buildDir/generated/res/react/${flavorPathSegment}release/${dir}")
                if (originalDir.exists()) {
                    File destDir = file("$buildDir/../src/main/res/${dir}")
                    ant.move(file: originalDir, tofile: destDir)
                }
            }

            moveFunc.curry("ldpi").call()
            moveFunc.curry("mdpi").call()
            moveFunc.curry("hdpi").call()
            moveFunc.curry("xhdpi").call()
            moveFunc.curry("xxhdpi").call()
            moveFunc.curry("xxxhdpi").call()
            moveRawFunc.curry("raw").call()
        }

@AbhishekNairOfficial where do you put this?

@AbhishekNairOfficial where do you put this?

node_modules/react-native/react.gradle.

Put this after the doFirst() block

@AbhishekNairOfficial where do you put this?

node_modules/react-native/react.gradle.

Put this after the doFirst() block

I guess it is not the right way to workaround this problem. Because this change will be overrided on next npm install. Even, the CI will not work

Someone tried commands below ?

cd android
./gradlew clean
cd ..

It worked for me

What worked for me was a combination of two comments:

I'm pasting this here for anyone who else might need it.

def flavorPathSegment = ""
android.productFlavors.all { flavor ->
if (targetName.toLowerCase().contains(flavor.name)) {
                    flavorPathSegment = flavor.name
            }
            }

            doLast {
                def moveFunc = { resSuffix ->
                    File originalDir = file("$buildDir/generated/res/react/${flavorPathSegment}release/drawable-${resSuffix}")
                    if (originalDir.exists()) {
                        File destDir = file("$buildDir/../src/main/res/drawable-${resSuffix}")
                        ant.move(file: originalDir, tofile: destDir)
                    }
                }
                def moveRawFunc = { dir ->
                    File originalDir = file("$buildDir/generated/res/react/${flavorPathSegment}release/${dir}")
                    if (originalDir.exists()) {
                        File destDir = file("$buildDir/../src/main/res/${dir}")
                        ant.move(file: originalDir, tofile: destDir)
                    }
                }

                moveFunc.curry("ldpi").call()
                moveFunc.curry("mdpi").call()
                moveFunc.curry("hdpi").call()
                moveFunc.curry("xhdpi").call()
                moveFunc.curry("xxhdpi").call()
                moveFunc.curry("xxxhdpi").call()
                moveRawFunc.curry("raw").call()
            }

oh man you saved my day <3

What worked for me was a combination of two comments:
I'm pasting this here for anyone who else might need it.

def flavorPathSegment = ""
android.productFlavors.all { flavor ->
if (targetName.toLowerCase().contains(flavor.name)) {
                    flavorPathSegment = flavor.name
            }
            }

            doLast {
                def moveFunc = { resSuffix ->
                    File originalDir = file("$buildDir/generated/res/react/${flavorPathSegment}release/drawable-${resSuffix}")
                    if (originalDir.exists()) {
                        File destDir = file("$buildDir/../src/main/res/drawable-${resSuffix}")
                        ant.move(file: originalDir, tofile: destDir)
                    }
                }
                def moveRawFunc = { dir ->
                    File originalDir = file("$buildDir/generated/res/react/${flavorPathSegment}release/${dir}")
                    if (originalDir.exists()) {
                        File destDir = file("$buildDir/../src/main/res/${dir}")
                        ant.move(file: originalDir, tofile: destDir)
                    }
                }

                moveFunc.curry("ldpi").call()
                moveFunc.curry("mdpi").call()
                moveFunc.curry("hdpi").call()
                moveFunc.curry("xhdpi").call()
                moveFunc.curry("xxhdpi").call()
                moveFunc.curry("xxxhdpi").call()
                moveRawFunc.curry("raw").call()
            }

oh man you saved my day <3

Haha Thanks. I kept coming to this thread almost on a daily basis, so I decided to make life easier for others as well.

Removing android/app/build folder and building again worked for me.

My solution:
Delete all files in /your_project/android/app/src/main/raw/res.
It 's work for me !

What worked for me was a combination of two comments:

I'm pasting this here for anyone who else might need it.

def flavorPathSegment = ""
android.productFlavors.all { flavor ->
if (targetName.toLowerCase().contains(flavor.name)) {
                    flavorPathSegment = flavor.name
            }
            }

            doLast {
                def moveFunc = { resSuffix ->
                    File originalDir = file("$buildDir/generated/res/react/${flavorPathSegment}release/drawable-${resSuffix}")
                    if (originalDir.exists()) {
                        File destDir = file("$buildDir/../src/main/res/drawable-${resSuffix}")
                        ant.move(file: originalDir, tofile: destDir)
                    }
                }
                def moveRawFunc = { dir ->
                    File originalDir = file("$buildDir/generated/res/react/${flavorPathSegment}release/${dir}")
                    if (originalDir.exists()) {
                        File destDir = file("$buildDir/../src/main/res/${dir}")
                        ant.move(file: originalDir, tofile: destDir)
                    }
                }

                moveFunc.curry("ldpi").call()
                moveFunc.curry("mdpi").call()
                moveFunc.curry("hdpi").call()
                moveFunc.curry("xhdpi").call()
                moveFunc.curry("xxhdpi").call()
                moveFunc.curry("xxxhdpi").call()
                moveRawFunc.curry("raw").call()
            }

thanks @AbhishekNairOfficial. it saved alot of time for me.

rm -rf ./android/app/src/main/res/drawable-*
rm -rf ./android/app/src/main/res/raw

So this does work, but then your images are missing, so if you rebundle your app then try to build you get the same error. Anyone have a legit fix for this yet?

Getting the same in v0.61.5 and as @wincod75 is saying, deleting raw and drawable folders isn't really a solution as all images are removed.

I get they are recreated for the release build but would be nice if they also worked when running react-native run-android --variant=release, that only works when they are in src/main it seams and then one can't build.

Anyone been able to solve on v0.61.5?

@gudbrand3 I eventually implemented the fix noted above to the "node_modules/react-native/react.gradle," file just so I could get a new build to the appstore, but this is absurd to have to do, not sure why this hasn't been fixed yet...

@wincod75 hear hear! I ended up with the same.. blah. I dont either get why it is not just included in the package until a better approach is found since people end up doing it anyway manually. Now it must be re-done for every deletion of node_modules and re npm install. #frustrating

Removing the drawable folders would not work for me since I had drawable resources that I needed to keep. My problem was with duplicate resources for everything in the android/app/src/main/res/raw folder and files that started with nodemodules... in the android/app/src/main/res/drawable folder. This solved the problem for me:

Adding to android/app/build.gradle:

...
project.ext.react = [
    ...
    bundleInRelease        : true,
    resourcesDirRelease   : "src/release/res",
    ...
]
...

Removing everything that started with node from android/app/src/main/drawable*:

rm android/app/src/main/drawable*/node*
rm -rf android/app/src/main/raw

Clean project, DO NOT RUN THE REACT NATIVE BUNDLE COMMAND, then re-build.

Hope this helps someone!

Oh, boy. Maybe an official fix is on the way?

I am having the same issue on v.0.61.4

in my case
first: I placed the following code in node_modules/react-native/react.gradle after 'doFirst'

doLast {
    def moveFunc = { resSuffix ->
        File originalDir = file("$buildDir/generated/res/react/release/drawable-${resSuffix}");
        if (originalDir.exists()) {
            File destDir = file("$buildDir/../src/main/res/drawable-${resSuffix}");
            ant.move(file: originalDir, tofile: destDir);
        }
    }
    moveFunc.curry("ldpi").call()
    moveFunc.curry("mdpi").call()
    moveFunc.curry("hdpi").call()
    moveFunc.curry("xhdpi").call()
    moveFunc.curry("xxhdpi").call()
    moveFunc.curry("xxxhdpi").call()
}

second: I deleted all contents of the folder android/app/src/main/res/raw
may be useful for someone

Oh, boy. Maybe an official fix is on the way?

I hope so, I'm using RN 0.62.1 and ran into this issue as well.

Adding the doLast code or deleting the drawables folders solves this issue.

I'm wondering to know what's the official recommendation?

Thank you

Removing the drawable folders would not work for me since I had drawable resources that I needed to keep. My problem was with duplicate resources for everything in the android/app/src/main/res/raw folder and files that started with nodemodules... in the android/app/src/main/res/drawable folder. This solved the problem for me:

Adding to android/app/build.gradle:

...
project.ext.react = [
    ...
    bundleInRelease        : true,
    resourcesDirRelease   : "src/release/res",
    ...
]
...

Removing everything that started with node from android/app/src/main/drawable*:

rm android/app/src/main/drawable*/node*
rm -rf android/app/src/main/raw

Clean project, DO NOT RUN THE REACT NATIVE BUNDLE COMMAND, then re-build.

Hope this helps someone!

The above solution helped me find a decent answer:

Step 1:

Adding to android/app/build.gradle:

... project.ext.react = [ ... bundleInRelease : true, resourcesDirRelease : "src/release/res", ... ] ...

Step 2:

Run the following command to delete all node_module files.

rm -rf drawable*/node*

Step 3:

Run this command to build and APK.

./gradlew assembleRelease

Step 4:

I made the following script to use in my package.json

"release-apk": "cd android && yarn remove-duplicate-files  && ./gradlew assembleRelease"

Hope this helps!
And thank you everyone who pitched in with their solutions

For anyone having the issue on non-image raw resources; I solved it by renaming the files with the same name but different extension.

My case

I had object.obj and object.mtl files. Even if the extensions are different, it threw duplicate resources error. Because android picks them up by file name. So, renaming object.mtl to object_material.mtl had finally resolved it for me.

It may be the same case for image resources too, try renaming them if you have images with identical names.

What worked for me was a combination of two comments:

I'm pasting this here for anyone who else might need it.

def flavorPathSegment = ""
android.productFlavors.all { flavor ->
if (targetName.toLowerCase().contains(flavor.name)) {
                    flavorPathSegment = flavor.name
            }
            }

            doLast {
                def moveFunc = { resSuffix ->
                    File originalDir = file("$buildDir/generated/res/react/${flavorPathSegment}release/drawable-${resSuffix}")
                    if (originalDir.exists()) {
                        File destDir = file("$buildDir/../src/main/res/drawable-${resSuffix}")
                        ant.move(file: originalDir, tofile: destDir)
                    }
                }
                def moveRawFunc = { dir ->
                    File originalDir = file("$buildDir/generated/res/react/${flavorPathSegment}release/${dir}")
                    if (originalDir.exists()) {
                        File destDir = file("$buildDir/../src/main/res/${dir}")
                        ant.move(file: originalDir, tofile: destDir)
                    }
                }

                moveFunc.curry("ldpi").call()
                moveFunc.curry("mdpi").call()
                moveFunc.curry("hdpi").call()
                moveFunc.curry("xhdpi").call()
                moveFunc.curry("xxhdpi").call()
                moveFunc.curry("xxxhdpi").call()
                moveRawFunc.curry("raw").call()
            }

targetName.toLowerCase().contains(flavor.name.toLowerCase()) work for me.
my flavor.name look like xxXX.

All of that dolast stuff was added in PRs #24518 and #24778, then removed again in #25363. Does anyone know why it was removed? This has been a LONG standing problem, and I thought they had it fixed.

Never mind, answers here:
https://github.com/facebook/react-native/issues/22234#issuecomment-504721069
and here: https://github.com/facebook/react-native/issues/25325

In case that helps, here are a couple of one-liners from my script (for Android X issue as well):

echo "Fixing java error: package android.support.v4.widget does not exist issue with Android X"

npm install jetifier
npx jetify

echo "Fixing duplicate resources issue"
rm -rf .../android/app/src/main/res/raw

cd .../node_modules/react-native

cat <<EOT >> react_gradle.patch
121a122,137
>             doLast {
>                 def moveFunc = { resSuffix ->
>                     File originalDir = file("$buildDir/generated/res/react/release/drawable-${resSuffix}");
>                     if (originalDir.exists()) {
>                         File destDir = file("$buildDir/../src/main/res/drawable-${resSuffix}");
>                         ant.move(file: originalDir, tofile: destDir);
>                     }
>                 }
>                 moveFunc.curry("ldpi").call()
>                 moveFunc.curry("mdpi").call()
>                 moveFunc.curry("hdpi").call()
>                 moveFunc.curry("xhdpi").call()
>                 moveFunc.curry("xxhdpi").call()
>                 moveFunc.curry("xxxhdpi").call()
>             }
> 
EOT

ls -l react_gradle.patch
patch react.gradle react_gradle.patch

And this solution helped me to create a release build: https://github.com/facebook/react-native/issues/26245#issuecomment-631382817

the following steps did it for me:

1. gradlew clean
2. npm install
3. gradlew bundleRelease
System:
    OS: macOS 10.15.3
    CPU: (4) x64 Intel(R) Core(TM) i5-5257U CPU @ 2.70GHz
    Memory: 221.64 MB / 8.00 GB
    Shell: 5.7.1 - /bin/zsh
  Binaries:
    Node: 12.13.1 - ~/.nvm/versions/node/v12.13.1/bin/node
    Yarn: 1.22.4 - ~/Documents/youpendo-app-bareworkflow/node_modules/.bin/yarn
    npm: 6.12.1 - ~/.nvm/versions/node/v12.13.1/bin/npm
    Watchman: 4.9.0 - /usr/local/bin/watchman
  Managers:
    CocoaPods: 1.9.3 - /usr/local/bin/pod
  SDKs:
    iOS SDK:
      Platforms: iOS 13.2, DriverKit 19.0, macOS 10.15, tvOS 13.2, watchOS 6.1
    Android SDK:
      API Levels: 28, 29
      Build Tools: 28.0.3, 29.0.2
      System Images: android-28 | Google APIs Intel x86 Atom, android-29 | Google APIs Intel x86 Atom
      Android NDK: Not Found
  IDEs:
    Android Studio: 3.6 AI-192.7142.36.36.6392135
    Xcode: 11.3.1/11C504 - /usr/bin/xcodebuild
  Languages:
    Java: 1.8.0_232 - /usr/bin/javac
    Python: 2.7.16 - /usr/bin/python
  npmPackages:
    @react-native-community/cli: ^4.8.0 => 4.10.0
    react: 16.11.0 => 16.11.0
    react-native: 0.62.2 => 0.62.2
  npmGlobalPackages:
    *react-native*: Not Found

I am getting the same error. Any solution ?

can someone please share the patch file for react native 0.62.2

I've created this patch for the React Native 0.63.2. If you have 'drawable-' folders, rename it for 'mipmap-', you can change the reference for it in the android/app/src/main/AndroidManifest.xml

Run this in the project's root to create the patch file

cat <<EOT >> react-native-0.63.2-react.gradle.patch 
@@ -147,6 +147,23 @@
                 jsSourceMapsDir.mkdirs()
             }

+            doLast {
+                def moveFunc = { resFolder ->
+                    File originalDir = file("\${buildDir}/generated/res/react/release/\${resFolder}");
+                    if (originalDir.exists()) {
+                        File destDir = file("\${buildDir}/../src/main/res/\${resFolder}");
+                        ant.move(file: originalDir, tofile: destDir);
+                    }
+                }
+                moveFunc.curry("drawable-ldpi").call()
+                moveFunc.curry("drawable-mdpi").call()
+                moveFunc.curry("drawable-hdpi").call()
+                moveFunc.curry("drawable-xhdpi").call()
+                moveFunc.curry("drawable-xxhdpi").call()
+                moveFunc.curry("drawable-xxxhdpi").call()
+                moveFunc.curry("raw").call()
+            }
+
             // Set up inputs and outputs so gradle can cache the result
             inputs.files fileTree(dir: reactRoot, excludes: inputExcludes)
             outputs.dir(jsBundleDir)
EOT

then apply the patch with

patch node_modules/react-native/react.gradle < react-native-0.63.2-react.gradle.patch

Also recommend to do

rm -Rf android/.gradle
cd android && gradlew clean

Copy node_ modules/react-native/react.gradle to android/app/react.gradle, then modify android/app/build.gradle and android/app/react.gradle:

android/app/build.gradle

- apply from: "../../node_modules/react-native/react.gradle"
+ apply from: "./react.gradle"

android/app/react.gradle

           doFirst {
                jsBundleDir.deleteDir()
                jsBundleDir.mkdirs()
                resourcesDir.deleteDir()
                resourcesDir.mkdirs()
                jsIntermediateSourceMapsDir.deleteDir()
                jsIntermediateSourceMapsDir.mkdirs()
                jsSourceMapsDir.deleteDir()
                jsSourceMapsDir.mkdirs()
            }
+
+           doLast {
+               def moveFunc = { resSuffix ->
+                   File originalDir = file("${resourcesDir}/drawable-${resSuffix}")
+                   if (originalDir.exists()) {
+                      File destDir = file("$buildDir/../src/main/res/drawable-${resSuffix}")
+                       ant.move(file: originalDir, tofile: destDir)
+                   }
+               }
+               def moveRawFunc = { dir ->
+                  File originalDir = file("${resourcesDir}/${dir}")
+                  if (originalDir.exists()) {
+                      File destDir = file("$buildDir/../src/main/res/${dir}")
+                      ant.move(file: originalDir, tofile: destDir)
+                  }
+              }
+
+              moveFunc.curry("ldpi").call()
+              moveFunc.curry("mdpi").call()
+              moveFunc.curry("hdpi").call()
+              moveFunc.curry("xhdpi").call()
+              moveFunc.curry("xxhdpi").call()
+              moveFunc.curry("xxxhdpi").call()
+              moveRawFunc.curry("raw").call()
+          }

This works for me in react-native 0.63.2

            doLast {
                def flavorPathSegment = ""
                println targetName.toLowerCase();
                android.productFlavors.all { flavor ->
                    if (targetName.toLowerCase().contains(flavor.name.toLowerCase())) {
                        flavorPathSegment = flavor.name
                    }
                }
                def moveFunc = { resFolder ->
                    File originalDir = file("${buildDir}/generated/res/react/${flavorPathSegment}/release/${resFolder}");
                    if (originalDir.exists()) {
                        File destDir = file("${buildDir}/../src/main/res/${resFolder}");
                        ant.move(file: originalDir, tofile: destDir);
                    }
                }

                moveFunc.curry("drawable").call()
                moveFunc.curry("drawable-ldpi").call()
                moveFunc.curry("drawable-mdpi").call()
                moveFunc.curry("drawable-hdpi").call()
                moveFunc.curry("drawable-xhdpi").call()
                moveFunc.curry("drawable-xxhdpi").call()
                moveFunc.curry("drawable-xxxhdpi").call()
                moveFunc.curry("raw").call()
            }


Here the patch file react-native+0.63.2.patch

diff --git a/node_modules/react-native/react.gradle b/node_modules/react-native/react.gradle
index 6441d93..1eb4645 100644
--- a/node_modules/react-native/react.gradle
+++ b/node_modules/react-native/react.gradle
@@ -147,6 +147,33 @@ afterEvaluate {
                 jsSourceMapsDir.mkdirs()
             }

+            doLast {
+                def flavorPathSegment = ""
+                println targetName.toLowerCase();
+                android.productFlavors.all { flavor ->
+                    if (targetName.toLowerCase().contains(flavor.name.toLowerCase())) {
+                        flavorPathSegment = flavor.name
+                    }
+                }
+                def moveFunc = { resFolder ->
+                    File originalDir = file("${buildDir}/generated/res/react/${flavorPathSegment}/release/${resFolder}");
+                    if (originalDir.exists()) {
+                        File destDir = file("${buildDir}/../src/main/res/${resFolder}");
+                        ant.move(file: originalDir, tofile: destDir);
+                    }
+                }
+
+                moveFunc.curry("drawable").call()
+                moveFunc.curry("drawable-ldpi").call()
+                moveFunc.curry("drawable-mdpi").call()
+                moveFunc.curry("drawable-hdpi").call()
+                moveFunc.curry("drawable-xhdpi").call()
+                moveFunc.curry("drawable-xxhdpi").call()
+                moveFunc.curry("drawable-xxxhdpi").call()
+                moveFunc.curry("raw").call()
+            }
+
+
             // Set up inputs and outputs so gradle can cache the result
             inputs.files fileTree(dir: reactRoot, excludes: inputExcludes)
             outputs.dir(jsBundleDir)
diff --git a/node_modules/react-native/scripts/.packager.env b/node_modules/react-native/scripts/.packager.env
new file mode 100644
index 0000000..21a13cf
--- /dev/null
+++ b/node_modules/react-native/scripts/.packager.env
@@ -0,0 +1 @@
+export RCT_METRO_PORT=8081
\ No newline at end of file

Hi all! Could someone make a PR for these changes? Editing node_modules is not ideal.

Hi all,

I am using react-native 0.63.2. I also faced this issue and tried editing react.gradle, deleted resources/drawable and all. But at last running the command gradlew assembleRelease worked for me.

I didn't run the react-native bundle command. gradlew assembleRelease is running the react-native bundle and building apk itself.

This works for me in react-native 0.63.2

            doLast {
                def flavorPathSegment = ""
                println targetName.toLowerCase();
                android.productFlavors.all { flavor ->
                    if (targetName.toLowerCase().contains(flavor.name.toLowerCase())) {
                        flavorPathSegment = flavor.name
                    }
                }
                def moveFunc = { resFolder ->
                    File originalDir = file("${buildDir}/generated/res/react/${flavorPathSegment}/release/${resFolder}");
                    if (originalDir.exists()) {
                        File destDir = file("${buildDir}/../src/main/res/${resFolder}");
                        ant.move(file: originalDir, tofile: destDir);
                    }
                }

                moveFunc.curry("drawable").call()
                moveFunc.curry("drawable-ldpi").call()
                moveFunc.curry("drawable-mdpi").call()
                moveFunc.curry("drawable-hdpi").call()
                moveFunc.curry("drawable-xhdpi").call()
                moveFunc.curry("drawable-xxhdpi").call()
                moveFunc.curry("drawable-xxxhdpi").call()
                moveFunc.curry("raw").call()
            }

Here the patch file react-native+0.63.2.patch

diff --git a/node_modules/react-native/react.gradle b/node_modules/react-native/react.gradle
index 6441d93..1eb4645 100644
--- a/node_modules/react-native/react.gradle
+++ b/node_modules/react-native/react.gradle
@@ -147,6 +147,33 @@ afterEvaluate {
                 jsSourceMapsDir.mkdirs()
             }

+            doLast {
+                def flavorPathSegment = ""
+                println targetName.toLowerCase();
+                android.productFlavors.all { flavor ->
+                    if (targetName.toLowerCase().contains(flavor.name.toLowerCase())) {
+                        flavorPathSegment = flavor.name
+                    }
+                }
+                def moveFunc = { resFolder ->
+                    File originalDir = file("${buildDir}/generated/res/react/${flavorPathSegment}/release/${resFolder}");
+                    if (originalDir.exists()) {
+                        File destDir = file("${buildDir}/../src/main/res/${resFolder}");
+                        ant.move(file: originalDir, tofile: destDir);
+                    }
+                }
+
+                moveFunc.curry("drawable").call()
+                moveFunc.curry("drawable-ldpi").call()
+                moveFunc.curry("drawable-mdpi").call()
+                moveFunc.curry("drawable-hdpi").call()
+                moveFunc.curry("drawable-xhdpi").call()
+                moveFunc.curry("drawable-xxhdpi").call()
+                moveFunc.curry("drawable-xxxhdpi").call()
+                moveFunc.curry("raw").call()
+            }
+
+
             // Set up inputs and outputs so gradle can cache the result
             inputs.files fileTree(dir: reactRoot, excludes: inputExcludes)
             outputs.dir(jsBundleDir)
diff --git a/node_modules/react-native/scripts/.packager.env b/node_modules/react-native/scripts/.packager.env
new file mode 100644
index 0000000..21a13cf
--- /dev/null
+++ b/node_modules/react-native/scripts/.packager.env
@@ -0,0 +1 @@
+export RCT_METRO_PORT=8081
\ No newline at end of file

thank you

Was this page helpful?
0 / 5 - 0 ratings