Search code examples
android-gradle-pluginreact-native

React Native : Error: Duplicate resources - Android


I was trying to create a release apk file from Android but when I create a release apk with PNG image I'm getting Duplicate Resource error. Initially I thought this is happening because I made a mistake in the existing project but when I created a new project with a single Image component itself I'm getting the Duplicate Resource error. Here are the steps I followed

  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 the 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/
  1. Then generate release apk using Generate Signed APK from Android Studio.

This will throw the following error:

[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

BUILD FAILED in 22s

Note: When you generate a release apk without any PNG image you will not get any error, it will create you the release apk.

Here are the other files code.

App.js

import React, {Component} from 'react';
import {Platform, StyleSheet, Image, View} from 'react-native';

export default class App extends Component {
  render() {
    return (
      <View style={styles.container}>
        <Image source={require('./assets/mario.png')} />
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1
  },
  welcome: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
  },
  instructions: {
    textAlign: 'center',
    color: '#333333',
    marginBottom: 5,
  },
});

package.json

{
  "name": "errorCheck",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "start": "node node_modules/react-native/local-cli/cli.js start",
    "test": "jest"
  },
  "dependencies": {
    "react": "16.6.0-alpha.8af6728",
    "react-native": "0.57.4"
  },
  "devDependencies": {
    "babel-jest": "23.6.0",
    "jest": "23.6.0",
    "metro-react-native-babel-preset": "0.49.0",
    "react-test-renderer": "16.6.0-alpha.8af6728"
  },
  "jest": {
    "preset": "react-native"
  }
}

Any solution for this?

Update:

Here are the other details

classpath 'com.android.tools.build:gradle:3.1.4'

ext {
        buildToolsVersion = "27.0.3"
        minSdkVersion = 16
        compileSdkVersion = 27
        targetSdkVersion = 26
        supportLibVersion = "27.1.1"
    }

distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip

Tried with Android Studio 3.0, 3.0.1, 3.1, 3.1.4 & 3.2


Solution

  • After trying a lot of solutions I found only Three solution is working. Here they are

    Solution 1:

    Clean the drawable folder from the terminal using Gradle. cd into the android folder, then run cmd ./gradlew clean

    Solution 2:

    After bundling delete the drawable folder from Android Studio. You could find this in android/app/src/main/res/drawable

    Solution 3:

    PLEASE DO NOT USE SOLUTION #2, AS PROPOSED BY THE ORIGINAL AUTHOR! All packages under node_modules are generated, and any changes you make will be lost when the react-native package is reinstalled / upgraded.

    In this solution you no need to delete any drawable folder. Just add the following code in the react.gradle file which you could find under node_modules/react-native/react.gradle path

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

    For reference I will add the full react.gradle file code here

    import org.apache.tools.ant.taskdefs.condition.Os
    
    def config = project.hasProperty("react") ? project.react : [];
    
    def cliPath = config.cliPath ?: "node_modules/react-native/local-cli/cli.js"
    def bundleAssetName = config.bundleAssetName ?: "index.android.bundle"
    def entryFile = config.entryFile ?: "index.android.js"
    def bundleCommand = config.bundleCommand ?: "bundle"
    def reactRoot = file(config.root ?: "../../")
    def inputExcludes = config.inputExcludes ?: ["android/**", "ios/**"]
    def bundleConfig = config.bundleConfig ? "${reactRoot}/${config.bundleConfig}" : null ;
    
    
    afterEvaluate {
        android.applicationVariants.all { def variant ->
            // Create variant and target names
            def targetName = variant.name.capitalize()
            def targetPath = variant.dirName
    
            // React js bundle directories
            def jsBundleDir = file("$buildDir/generated/assets/react/${targetPath}")
            def resourcesDir = file("$buildDir/generated/res/react/${targetPath}")
    
            def jsBundleFile = file("$jsBundleDir/$bundleAssetName")
    
            // Additional node and packager commandline arguments
            def nodeExecutableAndArgs = config.nodeExecutableAndArgs ?: ["node"]
            def extraPackagerArgs = config.extraPackagerArgs ?: []
    
            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()
                }
    
                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()
                }
    
                // Set up inputs and outputs so gradle can cache the result
                inputs.files fileTree(dir: reactRoot, excludes: inputExcludes)
                outputs.dir jsBundleDir
                outputs.dir resourcesDir
    
                // Set up the call to the react-native cli
                workingDir reactRoot
    
                // Set up dev mode
                def devEnabled = !(config."devDisabledIn${targetName}"
                    || targetName.toLowerCase().contains("release"))
    
                def extraArgs = extraPackagerArgs;
    
                if (bundleConfig) {
                    extraArgs = extraArgs.clone()
                    extraArgs.add("--config");
                    extraArgs.add(bundleConfig);
                }
    
                if (Os.isFamily(Os.FAMILY_WINDOWS)) {
                    commandLine("cmd", "/c", *nodeExecutableAndArgs, cliPath, bundleCommand, "--platform", "android", "--dev", "${devEnabled}",
                        "--reset-cache", "--entry-file", entryFile, "--bundle-output", jsBundleFile, "--assets-dest", resourcesDir, *extraArgs)
                } else {
                    commandLine(*nodeExecutableAndArgs, cliPath, bundleCommand, "--platform", "android", "--dev", "${devEnabled}",
                        "--reset-cache", "--entry-file", entryFile, "--bundle-output", jsBundleFile, "--assets-dest", resourcesDir, *extraArgs)
                }
    
                enabled config."bundleIn${targetName}" ||
                    config."bundleIn${variant.buildType.name.capitalize()}" ?:
                    targetName.toLowerCase().contains("release")
            }
    
            // Expose a minimal interface on the application variant and the task itself:
            variant.ext.bundleJsAndAssets = currentBundleTask
            currentBundleTask.ext.generatedResFolders = files(resourcesDir).builtBy(currentBundleTask)
            currentBundleTask.ext.generatedAssetsFolders = files(jsBundleDir).builtBy(currentBundleTask)
    
            // registerGeneratedResFolders for Android plugin 3.x
            if (variant.respondsTo("registerGeneratedResFolders")) {
                variant.registerGeneratedResFolders(currentBundleTask.generatedResFolders)
            } else {
                variant.registerResGeneratingTask(currentBundleTask)
            }
            variant.mergeResources.dependsOn(currentBundleTask)
    
            // packageApplication for Android plugin 3.x
            def packageTask = variant.hasProperty("packageApplication")
                ? variant.packageApplication
                : tasks.findByName("package${targetName}")
    
            def resourcesDirConfigValue = config."resourcesDir${targetName}"
            if (resourcesDirConfigValue) {
                def currentCopyResTask = tasks.create(
                    name: "copy${targetName}BundledResources",
                    type: Copy) {
                    group = "react"
                    description = "copy bundled resources into custom location for ${targetName}."
    
                    from resourcesDir
                    into file(resourcesDirConfigValue)
    
                    dependsOn(currentBundleTask)
    
                    enabled currentBundleTask.enabled
    
    
                }
    
                packageTask.dependsOn(currentCopyResTask)
            }
    
            def currentAssetsCopyTask = tasks.create(
                name: "copy${targetName}BundledJs",
                type: Copy) {
                group = "react"
                description = "copy bundled JS into ${targetName}."
    
                if (config."jsBundleDir${targetName}") {
                    from jsBundleDir
                    into file(config."jsBundleDir${targetName}")
                } else {
                    into ("$buildDir/intermediates")
                    into ("assets/${targetPath}") {
                        from jsBundleDir
                    }
    
                    // Workaround for Android Gradle Plugin 3.2+ new asset directory
                    into ("merged_assets/${targetPath}/merge${targetName}Assets/out") {
                        from jsBundleDir
                    }
                }
    
                // mergeAssets must run first, as it clears the intermediates directory
                dependsOn(variant.mergeAssets)
    
                enabled currentBundleTask.enabled
            }
    
            packageTask.dependsOn(currentAssetsCopyTask)
        }
    }
    

    Credit: ZeroCool00 mkchx