React Native : Error: Duplicate resources - Android
Asked Answered
S

35

116

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

Shortwinded answered 10/11, 2018 at 13:59 Comment(8)
did u run application using react-native run-android ?Tuttifrutti
Yes @Lucefer I don't have any problem to run the app directly using react-native run-android only thing is when I generate the release apk I'm getting the above errorShortwinded
what is the android studio version you are using?Worry
Its Android Studio 3.2.1 @FlorinDobreShortwinded
Check my answer from here: https://mcmap.net/q/189328/-react-native-error-duplicate-resources-assets-coming-in-some-screens-and-not-coming-in-others-in-android-release-apk Use Android studio 3.1.4 Android 3.2 has problem at building RN 0.57 projectsWorry
Possible duplicate of React Native assets coming in some screens and not coming in others in android release APKWorry
@FlorinDobre I have tried with Android Studio 3.0, 3.0.1, 3.1, 3.1.4 & 3.2 still getting the same errorShortwinded
please check my answer to a duplicate question: https://mcmap.net/q/103299/-react-native-duplicate-resourcesChristcrossrow
S
134

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

Shortwinded answered 12/11, 2018 at 10:46 Comment(8)
@Jeffrey-Rajan - I wanted to give you (along with ZeroCool00 and mkchx) credit as the inspiration for a PR I made to react-native which was just recently merged - github.com/facebook/react-native/commit/… - I extended your proposed patch above to handle flavors, and it's on react-native@master now, so hopefully this won't bite people in the future.Sansculotte
Seems extreme to me to modify a React file (node_modules folder cough cough...). Make sure you look into your own setup before going with rogue solutions. Could be as simple as: https://mcmap.net/q/102548/-react-native-android-duplicate-file-error-when-generating-apkSweetener
Please DO NOT use Solution #2. Developers should not be editing code in node_modules, as the packages installed in this folder are generated, and added code will be overwritten / lost when the package is upgraded / uninstalled.Vickery
Solution 2 makes my icons disappearNettle
Is there any way to use solution 1 without deleting all the folders? I need them for the notification iconsNettle
I also am using the drawable folders for resources on the native part (notification icons, launcher layouts, launch icons, etc). What worked for me is not deleting the whole drawable* folders but only delete the files inside that start with prefix "node_modules" (assets from the dependencies) and "src_" (assets from your project) and then assemble release just as usual.Swingeing
Just wondering, if it was auto-generated, why make it an error in the first place?Woolly
That seems to be working. Note: you may need to add "raw" to that list.Glider
C
167

(UPDATED)

This solution works for me

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

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

In my case, the build failed because there are duplicated resources in my Android project (in Android folder), these two lines are necessary to remove duplicated resources, that's all.

Chequer answered 17/9, 2019 at 13:26 Comment(17)
an explanation of how/why this solves the problem would make this a better answer...Backfire
This looks like this just deletes your assets.Cis
Didn't work for me. Tried react-native bundle ... again after this and got the same error when trying to build.Cermet
what if i am using assets in there? i am not sure why i ahve to delete stuff inside?Paraffinic
WHy do we have to delete these directories? these are valid directries for assets needed in the nativ android project. if i delete this then any layouts that use these drawables are brokenParaffinic
@Jonathan, Why you need to put assets in your android directory? Assets are added in you src directory.Chequer
The asset directory is inside srcParaffinic
@Jonathan, Of course, but these lines remove duplicated resources in android directory not in your src directoryChequer
i heard you have to completely remove the foldersParaffinic
Yes, but images are recreated by React Native when you rebuild the project, and ithe build failed when these resources are presents, that's why I need to delete them before a new buildChequer
@Backfire that just remove the folders.Polley
One of my react project works well but the another one has the problem. This may be not the general solution as it works.Russianize
Removing drawable-* worked and fixed the issue. For us deleting /raw caused the build to fail. We had two ssl certs in there that did not get copied over automatically on next build.Palpable
You can find dublications by looking in res folder no need to delete all drawables may be your app need some of themeStirpiculture
Also next time you want to build, if you omit ` --assets-dest android/app/src/main/res/` from the following command 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/ you won't get the error.Countrydance
this command does not work on windowsStrickle
I think we need just to delete the ./android/app/src/main/res/drawable folder not the rest ./android/app/src/main/res/drawable-* because it has some files generated automatically but the rest are assets needed in the projectFreehearted
S
134

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

Shortwinded answered 12/11, 2018 at 10:46 Comment(8)
@Jeffrey-Rajan - I wanted to give you (along with ZeroCool00 and mkchx) credit as the inspiration for a PR I made to react-native which was just recently merged - github.com/facebook/react-native/commit/… - I extended your proposed patch above to handle flavors, and it's on react-native@master now, so hopefully this won't bite people in the future.Sansculotte
Seems extreme to me to modify a React file (node_modules folder cough cough...). Make sure you look into your own setup before going with rogue solutions. Could be as simple as: https://mcmap.net/q/102548/-react-native-android-duplicate-file-error-when-generating-apkSweetener
Please DO NOT use Solution #2. Developers should not be editing code in node_modules, as the packages installed in this folder are generated, and added code will be overwritten / lost when the package is upgraded / uninstalled.Vickery
Solution 2 makes my icons disappearNettle
Is there any way to use solution 1 without deleting all the folders? I need them for the notification iconsNettle
I also am using the drawable folders for resources on the native part (notification icons, launcher layouts, launch icons, etc). What worked for me is not deleting the whole drawable* folders but only delete the files inside that start with prefix "node_modules" (assets from the dependencies) and "src_" (assets from your project) and then assemble release just as usual.Swingeing
Just wondering, if it was auto-generated, why make it an error in the first place?Woolly
That seems to be working. Note: you may need to add "raw" to that list.Glider
F
70

For generating debug apk

"debug-build": "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/ && cd android && ./gradlew assembleDebug && cd .."

For generating release apk

"release-build": "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/build/intermediates/res/merged/release/ && rm -rf android/app/src/main/res/drawable-* && rm -rf android/app/src/main/res/raw/* && cd android && ./gradlew assembleRelease && cd .."

Works fine for my react-native >= 0.61.2 project by using the above code on my scripts section of package.json file.

Fusspot answered 9/12, 2019 at 11:50 Comment(2)
This approach should be the accepted answer. However, you might be able to use ./gradlew clean instead.Vickery
windows "debug-build": "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/ && cd android && ./gradlew assembleDebug && cd app/src/main/res/ && rmdir /S /Q raw && rmdir /S /Q drawable-mdpi" "release-build": "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/build/intermediates/res/merged/release/ && cd android && ./gradlew assembleRelease && cd .."Fusspot
B
15

The accepted answer will work, however it does not take into account that modifying a node package means that if you update your change will be lost (as well as being against best-practices, you should extend the module somehow).

This is originally from React-native android release error: duplicate resource

  1. Create folder fixAndroid in the android folder of your project ({project-root}/android/fixAndroid).

  2. Create file android-gradle-fix in the fixAndroid folder of your project ({project-root}/android/fixAndroid/android-gradle-fix).

doLast {
    def moveFunc = { resSuffix ->
        File originalDir = file("${resourcesDir}/drawable-${resSuffix}")
        if (originalDir.exists()) {
            File destDir = file("${resourcesDir}/drawable-${resSuffix}-v4")
            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
  1. Create file android-release-fix.js in the fixAndroid folder you created:
const fs = require('fs')

try {
    var curDir = __dirname
    var rootDir = process.cwd()

    var file = `${rootDir}/node_modules/react-native/react.gradle`
    var dataFix = fs.readFileSync(`${curDir}/android-gradle-fix`, 'utf8')
    var data = fs.readFileSync(file, 'utf8')

    var doLast = "doLast \{"
    if (data.indexOf(doLast) !== -1) {
        throw "Already fixed."
    }

    var result = data.replace(/\/\/ Set up inputs and outputs so gradle can cache the result/g, dataFix);
    fs.writeFileSync(file, result, 'utf8')
    console.log('Android Gradle Fixed!')
} catch (error) {
    console.error(error)
}
  1. Add script to package.json scripts section:
"postinstall": "node ./android/fixAndroid/android-release-fix.js"

This will find and insert content of “android-gradle-fix” file into node_modules/react-native/react.gradle.

  1. Run npm install from the root of your project.
  2. Run rm -rf android/app/src/main/res/drawable-* from the root of your project.

Now you can bundle the release with either React Native at the console or Android Studio:

React Native command line

cd {project-root}/android
./gradlew/bundleRelease

Android Studio

  1. Open folder android in Android Studio and build project.
  2. Select Build/Generate signed APK to build release.
Backfire answered 29/8, 2019 at 15:50 Comment(0)
A
12
  1. Delete drawable-xxx folders
  2. Delete raw

inside src -> main -> res folder then

  1. run this command in terminal:

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

  1. then generate signed apk using keystore, alias and passwords using terminal or android studio
Ablution answered 1/11, 2020 at 21:3 Comment(1)
can you explain what the script in point no.3 does?Nitrite
P
10

who are facing the same issue in RN! I think it's absolutely awful that this issue stands here for so long time already, but I want to share the way to solve it after investigation of different solutions.

Jeffrey Rajan is absolutely right about the possible solutions here https://mcmap.net/q/187470/-react-native-error-duplicate-resources-android

I think it's super bad to change react.gradle file in node_modules and it leads to many many different issues with the maintenance of this RN project. So I would recommend to chose the first option - to use bash command to remove that folder before running build.

I want to share what I've done in my project and maybe you can reuse the same approach:

In ./package.json:

scripts: {
   "build": "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/
          && rm -rf ./android/app/src/main/res/drawable-mdpi/
          && rm -rf ./android/app/src/main/res/raw/",

   "release": "yarn build && cd ./android && ./gradlew bundleRelease"
}

and the release is running by doing yarn release.

These lines are important:

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

they remove duplicated resources from the build step before bundleRelease is running. The solution is tested with RN 0.57, 0.58, 0.59 and 0.60

Enjoy!

Pyrogen answered 31/8, 2019 at 10:34 Comment(0)
P
10

for latest version of React-Native and gradle, you don't need to bundle your assets. Once you are done with the code, just cd into the android folder and run:

./gradlew assembleRelease

The assets are automatically bundled while the above command is been executed. The duplicate resources error shows up because you have explicitly bundled before and running the above command bundles again hence the error.

Pennyworth answered 8/9, 2020 at 14:2 Comment(0)
V
7

tolotrasmile's answer worked for me.

I included it in my little bash script that I run whenever I want to build & install Android

cd "$PROJECT_DIRECTORY"
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
cd ..

rm -rf "$PROJECT_DIRECTORY"/android/app/src/main/res/drawable-*
rm -rf "$PROJECT_DIRECTORY"/android/app/src/main/res/raw

cd "$PROJECT_DIRECTORY"/android/
./gradlew clean
./gradlew assembleRelease
cd ../../

adb install -r "$PROJECT_DIRECTORY"/android/app/build/outputs/apk/release/app-release.apk
Volant answered 19/9, 2019 at 18:25 Comment(0)
S
5

In my case, it worked after adding a few lines to Jaffrey's Answer

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);
    }
}
Sprocket answered 21/11, 2020 at 16:9 Comment(0)
M
4

was error with release build /android/app/src/main/res/raw/app.json:

[raw/app] /android/app/build/generated/res/react/release/raw/app.json: Error: Duplicate resources

deleted /android/app/src/main/res/raw/app.json
then ran ./gradlew clean in android folder
and then ran ./gradlew bundleRelease

Misshapen answered 4/3, 2021 at 12:51 Comment(0)
B
3

For me it was a cache issue. The following command worked for me

cd into android folder

Run ./gradlew clean

Baines answered 28/1, 2020 at 8:39 Comment(0)
U
3

Try to use another directory for assets destination (for example res_temp).

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

I am also adding res_temp in gitignore. Tested in Gradle 6.0.1 and RN 0.62.2

Ululant answered 1/9, 2020 at 11:18 Comment(0)
R
2

My version react native is: 0.66.3 , I had fix trought commands:

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

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

Then run :

./gradlew clean

Finally: Generate Signed Bundle from Android Studio and works.

Recognition answered 22/4, 2022 at 2:59 Comment(0)
S
2

This solution we work for user who are using github or any other version control system.

The reason for this issue was github. if we clean the project then and create a release build it was crashing due to missing resources. on the other hand if i run this command.

npx 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/build/intermediates/res/merged/release/ && cd android && ./gradlew assembleRelease && cd ..

it well create the issue duplicate resources.

so before creating a release version of your appplication. clean you project with commad gradlew clean and delete the file android\app\src\main\assets\index.android.bundle . now run this commnad

npx 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

after running this command open vs code and delete all the newly added files except index.android.bundle

now you can create a release of you app. it will work fine

Strickle answered 27/7, 2023 at 4:56 Comment(0)
A
1

Solution.
I just clean gradlew and starts working no need to change anything in.
/node_modules/react-native/react.gradle

Steps
1) cd android && ./gradlew clean && cd ..
2) react-native run-android

"dependencies": {
    "react": "16.11.0",
    "react-native": "0.62.2",
}
Acid answered 11/5, 2020 at 17:24 Comment(1)
it's only release problem not debugShulock
H
1

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 cleaning gradle and running the command gradlew assembleRelease worked.

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

Haematoid answered 30/9, 2020 at 12:23 Comment(0)
G
1

Spent ages on this! None of the proposed solutions were an immediate fix for me.

So I moved out all the dirs that were getting mentioned in the "duplicate resource" error message:

mv ./android/app/src/main/res/drawabl* /tmp

mv ./android/app/src/main/res/raw /tmp

then I ran a

./gradlew clean

then I ran a build:

/gradlew bundleRelease

the build was giving me a couple of errors along the lines of 'could not find a specific file in a specific place', so for each of these files I found them in the tmp/drawable folders and copied them to the exact path that the error message said it expected them to be e.g (you'll have to customise this according to the error message you get):

cp /tmp/drawable-mdpi/launch_screen.png app/src/main/res/drawable/

then:

./gradlew clean

/gradlew bundleRelease

And it finally buily successfully

Gandhiism answered 1/10, 2021 at 13:32 Comment(0)
A
1

for me go to dir: android/app/src/main/assets/fonts

check .ttf files you expect, delete .ttf of vector icons

Aeroneurosis answered 17/3, 2022 at 11:3 Comment(0)
H
1

I had also this problem and to solve it, I have deleted resource folders and used gradlew clean.

I ended up with this Windows cmd script for compiling my app:

@echo off

call 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

rmdir android\app\src\main\res\drawable-hdpi /s/q
rmdir android\app\src\main\res\drawable-mdpi /s/q
rmdir android\app\src\main\res\drawable-xhdpi /s/q
rmdir android\app\src\main\res\drawable-xxhdpi /s/q
rmdir android\app\src\main\res\drawable-xxxhdpi /s/q
rmdir android\app\src\main\res\raw /s/q

cd android
call gradlew clean
REM call gradlew assembleRelease
call gradlew bundle
cd ..

dir /o:d android\app\build\outputs\apk\release
dir /o:d android\app\build\outputs\bundle
Hag answered 12/4, 2022 at 10:10 Comment(0)
C
1

If the above answers are not working and if the issue is similar to the below error

> Task :app:mergeReleaseResources FAILED
ERROR: [drawable-mdpi-v4/node_modules_reactnative_libraries_newappscreen_components_logofgdsfsdfsdfds] 
/Users/emmsdan/Repo/untitledApp/android/app/src/main/res/drawable-mdpi/node_modules_reactnative_libraries_newappscreen_components_logofgdsfsdfsdfds.png 
[drawable-mdpi-v4/node_modules_reactnative_libraries_newappscreen_components_logofgdsfsdfsdfds]
/Users/emmsdan/Repo/untitledApp/android/app/build/generated/res/react/release/drawable-mdpi/
node_modules_reactnative_libraries_newappscreen_components_logofgdsfsdfsdfds.png: Resource and asset merger: Duplicate resources

Not the most elegant solution but this worked for me.

Delete this

rm -rf rmnode_modules/react-native/Libraries/NewAppScreen

I had similar issues, even on a newly created react-native app. Deleting NewAppScreen and everywhere it was called fixed it for me.

Coronel answered 26/4, 2022 at 8:13 Comment(0)
M
1

Good day all. It may be late but I encountered a strange situation while bundling/assembling my project on RN (0.71.x). Using the information from your project asset [drawable-mdpi-v4/assets_mario].

I discovered that when bundling, the configuration removes the extensions reference from the filename. So if you have assets_mario.jpg and assets_mario.png being bundled, the final configuration reference for both becomes [drawable-mdpi-v4/assets_mario] with two instances and thus, duplicate resources violation.

Somehow, all your assets must have unique names. Check on this as well.

Mustard answered 31/3, 2023 at 4:42 Comment(0)
W
0

Use com.android.tools.build:gradle:3.1.4 It should work. RN 0.57 has problem with building in 3.2

This question is a possible duplicate of:

React Native Error: Duplicate resources, assets coming in some screens and not coming in others in android release APK

If it still doesn't work try to use RN 0.57.2, I am using it and creating releases works very well with these deps:

   "dependencies": {
    "react": "16.5.0",
    "react-native": "0.57.2",
    .......
  }

  "devDependencies": {
    "@babel/core": "^7.0.0",
    "@babel/plugin-proposal-class-properties": "^7.0.0",
    "@babel/plugin-proposal-decorators": "^7.0.0",
    "@babel/plugin-proposal-do-expressions": "^7.0.0",
    "@babel/plugin-proposal-export-default-from": "^7.0.0",
    "@babel/plugin-proposal-export-namespace-from": "^7.0.0",
    "@babel/plugin-proposal-function-bind": "^7.0.0",
    "@babel/plugin-proposal-function-sent": "^7.0.0",
    "@babel/plugin-proposal-json-strings": "^7.0.0",
    "@babel/plugin-proposal-logical-assignment-operators": "^7.0.0",
    "@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0",
    "@babel/plugin-proposal-numeric-separator": "^7.0.0",
    "@babel/plugin-proposal-object-rest-spread": "^7.0.0",
    "@babel/plugin-proposal-optional-chaining": "^7.0.0",
    "@babel/plugin-proposal-pipeline-operator": "^7.0.0",
    "@babel/plugin-proposal-throw-expressions": "^7.0.0",
    "@babel/plugin-syntax-dynamic-import": "^7.0.0",
    "@babel/plugin-syntax-import-meta": "^7.0.0",
    "@babel/plugin-syntax-object-rest-spread": "^7.0.0",
    "@babel/plugin-transform-runtime": "^7.0.0",
    "@babel/preset-env": "^7.0.0",
    "@babel/preset-flow": "^7.0.0",
    "@babel/register": "^7.0.0",
    "babel-core": "^7.0.0-bridge.0",
    "babel-preset-react-native-stage-0": "^1.0.1",
    .....
}

Gradle deps:

classpath 'com.android.tools.build:gradle:3.1.4'
classpath "io.realm:realm-gradle-plugin:4.0.0"

App gradle

compileSdkVersion 27
    buildToolsVersion "27.0.3"

    defaultConfig {
        applicationId "de.burda.buntede"
        minSdkVersion 17
        targetSdkVersion 27

Gradle wrapper props:

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

Worry answered 10/11, 2018 at 17:47 Comment(1)
I tried with Android Studio 3.1 still I'm getting the same problem. Also this is not the duplicate question. I have updated my question with additional details as well.Shortwinded
U
0

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.

Umbrella answered 1/3, 2019 at 5:18 Comment(0)
N
0

So basically 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()
}
Nutrilite answered 24/7, 2019 at 23:3 Comment(1)
Do not edit code in the node_modules folder - packages in this folder are generated, and when the package is upgraded, reinstalled, etc - the code changes will be lost.Vickery
L
0

The solutions like patching the react.gradle file may work but they are just workarounds. The real solution lies in figuring out the problem.

  1. If its only happening while creating release build then chances are you're bundling separately. If you're upgrading from older versions of react native which didn't have a react.gradle file you'd have to bundle separately and then do 'cd android && ./gradlew assembleRelease' but with the introduction of react.gradle the bundling process has now been taken care by the react.gradle file, so don't bundle separately just run 'cd android && ./gradlew clean && ./gradlew assembleRelease' to build the release apk.

  2. If this happens in debug also then chances are some generated assets or all of the generated assets have been pushed and currently reside in your directory. So i would suggest just delete all of them from the respective directory and if they are tracked by git try adding them back one by one. We had some assets starting with src_assets_blahblah which we had to delete since they were already getting generated during the build process.

Linneman answered 30/10, 2020 at 20:21 Comment(0)
J
0

None of the suggested solutions works for me

In my case it was related to .gradle folder. Before getting this error i have moved the folder to another disk, but some files failed to copy to the destination folder.

I just cut the old folder and merge it with the destination folder.

Jaunita answered 19/3, 2021 at 15:31 Comment(0)
P
0

In my case, raw directory was containing some duplicates. So, I have to add a different function for that as well, name of added function is moveFuncRaw below worked perfectly for me.

        doLast {

            // Address issue #22234 by moving generated resources into build dir so they are in one spot, not duplicated
                def moveFuncRaw = { dirName ->
                    File originalDir = file("$buildDir/generated/res/react/release/${dirName}");
                    if (originalDir.exists()) {
                        File destDir = file("$buildDir/../src/main/res/${dirName}");
                        ant.move(file: originalDir, tofile: destDir);
                    }
                }
                
                // Address issue #22234 by moving generated resources into build dir so they are in one spot, not duplicated
                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()
                moveFuncRaw.curry("raw").call()
        }

One more thing to realize that doLast do not gets called every time as there could be some conditions on it like in my case there were, so you have to call separately to get it executed.

Parceling answered 13/1, 2022 at 3:49 Comment(0)
L
0

Accepted answer's solution 1 will work but it will add some more things to your assets.

Solution 1:

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

This will solve the initial problem of old code rendering but will create another issue usually: the issue of duplicate resources.

Check your androids res folder there will be added additional assets in your mipmap filders and res folder, delete all the additional added assets from the folders and run the release version of app ad you will be good to go.

Licence answered 17/10, 2022 at 10:43 Comment(0)
B
0

This issue comes after you run 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

So only thing you want to do is remove the resources files which builds with it.

If you use git, stage index.android.bundle and discard other new resource files.

Blithe answered 6/12, 2022 at 18:33 Comment(0)
G
0

A slight addition: sometimes you end up with a "raw" in the resources folder. That has to go too. So a slight modification to the existing react.gradle script:

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()
}
Glider answered 18/12, 2022 at 23:40 Comment(0)
N
0

Open android studio and check res and res(generated) folders. For me in res folder was duplicated files in raw folder. I manually deleted raw folder from res and see BUILD SUCCESSFUL

Neoarsphenamine answered 6/3, 2023 at 5:53 Comment(0)
B
-1

By adding this is in /node_modules/react-native/react.gradle

After DoFirst Paste This Code

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

}

Badoglio answered 11/2, 2020 at 8:35 Comment(1)
Do not edit code in the node_modules folder - packages in this folder are generated, and when the package is upgraded, reinstalled, etc - the code changes will be lost.Vickery
E
-1

It's not needed to add any code in the react-native module.

By default, the app/build.gradle set the property bundleInRelease as true. This means that if you run the react native bundle before the build, you will get this error.

To fix this issue, make sure that your app/build.gradle has the following code:

project.ext.react = [
   bundleInRelease: false
]

You can find more details in: https://github.com/react-native-community/cli/blob/master/docs/commands.md

Ethelynethene answered 6/6, 2022 at 19:9 Comment(0)
I
-1

don't remove all element inside drawable folders , because sometimes you have you splash screen or app icon inside this folder , so the best solution is to remove just generated images (that begins with node_modules---.png or src--.png) ; so you need to run just this command from the android folder of your app :

rm -rf  app/src/main/res/draw*/src*.png 
rm -rf  app/src/main/res/draw*/node*.png
Indeclinable answered 12/11, 2023 at 13:31 Comment(0)
C
-4

I encountered the same problem after using 0.57.7, Look at the node_modules/react-native/react.gradle file, The resource output directory is $buildDir/generated/res/react/${targetPath} . Look at the address from the log as app/build/generated/res/react/release,

Command 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/

The resource output directory is android/app/src/main/res/

The problem is here.

I solved this:

  1. Delete duplicate files in android/app/res (If your resources are imported from React Native, you can directly delete the directory under res).

  2. Delete the App/build folder.

  3. Will 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/

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

No longer specify --assets-dest Run command

  1. Use Android Studio's Generate Signed APK to package properly.

the above

Coachandfour answered 18/12, 2018 at 3:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.