react-native icon indicating copy to clipboard operation
react-native copied to clipboard

[Android] Error: Duplicate resources

Open jeffreyrajanofficial opened this issue 6 years ago • 113 comments

  • [x] Review the documentation: https://facebook.github.io/react-native
  • [x] Search for existing issues: https://github.com/facebook/react-native/issues
  • [x] Use the latest React Native release: https://github.com/facebook/react-native/releases

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

jeffreyrajanofficial avatar Nov 10 '18 12:11 jeffreyrajanofficial

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

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

ZeroCool00 avatar Nov 11 '18 10:11 ZeroCool00

@ZeroCool00 wont that affect the images in Android?

jeffreyrajanofficial avatar Nov 12 '18 07:11 jeffreyrajanofficial

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()
}

echaritonidis avatar Nov 12 '18 09:11 echaritonidis

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

jeffreyrajanofficial avatar Nov 12 '18 10:11 jeffreyrajanofficial

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.

vivek-walecha-657-zz avatar Dec 21 '18 11:12 vivek-walecha-657-zz

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 avatar Dec 21 '18 11:12 jeffreyrajanofficial

@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.

vivek-walecha-657-zz avatar Dec 24 '18 10:12 vivek-walecha-657-zz

@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.

vivek-walecha-657-zz avatar Dec 24 '18 11:12 vivek-walecha-657-zz

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

vivek-walecha-657-zz avatar Jan 03 '19 13:01 vivek-walecha-657-zz

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

abhishekgargx avatar Jan 14 '19 13:01 abhishekgargx

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

mikehardy avatar Jan 24 '19 04:01 mikehardy

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.

rahulkumar1409 avatar Mar 01 '19 05:03 rahulkumar1409

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

mikehardy avatar Mar 14 '19 15:03 mikehardy

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 avatar Mar 26 '19 12:03 dragosroua

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

mikehardy avatar Mar 26 '19 12:03 mikehardy

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 avatar Mar 26 '19 12:03 dragosroua

@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...

mikehardy avatar Mar 26 '19 12:03 mikehardy

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 avatar Mar 28 '19 12:03 zakabluk

@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?

mikehardy avatar Mar 28 '19 12:03 mikehardy

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

furedal avatar Apr 08 '19 10:04 furedal

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...

mikehardy avatar Apr 08 '19 13:04 mikehardy

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

wmailn avatar Apr 10 '19 01:04 wmailn

@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

mikehardy avatar Apr 10 '19 02:04 mikehardy

@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

wmailn avatar Apr 10 '19 02:04 wmailn

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)

mikehardy avatar Apr 18 '19 21:04 mikehardy

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 avatar Apr 25 '19 16:04 Dbroqua

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

lubo-makky avatar Apr 25 '19 17:04 lubo-makky

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?

mikehardy avatar Apr 25 '19 18:04 mikehardy

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

Dbroqua avatar Apr 25 '19 18:04 Dbroqua

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...

mikehardy avatar Apr 25 '19 18:04 mikehardy