Attempting to upgrade React Native from 0.63.2 to 0.65.2 has run into a number of issues, and I find myself playing android build wack-a-mole. The latest build error that I'm attempting to circumvent is:
There is an existing article ( The value for property 'resValues' cannot be changed any further. after upgrading gradle from 4.1.3 to 4.2.1 ) however it indicates to remove or update the "heapanalytics" plugin, which my project does not have.
I have attempted to uninstall / reinstall npm library "expo-constants" which seems to be where the problem is occurring.
Has anyone run into any similar problem? Or can anyone better explain what might cause this issue, or potential steps to resolve? Any help or suggestions are appreciated!
Some additional info about the environment: android > build.gradle
buildscript {
ext {
googlePlayServicesVersion = "15.0.1"
buildToolsVersion = "30.0.2"
minSdkVersion = 23
compileSdkVersion = 30
targetSdkVersion = 30
ndkVersion = "20.1.5948944"
}
repositories {
google()
mavenCentral()
}
dependencies {
classpath('com.android.tools.build:gradle:4.2.1')
classpath('com.google.gms:google-services:4.3.3')
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
mavenCentral()
mavenLocal()
maven {
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
url("$rootDir/../node_modules/react-native/android")
}
maven {
// Android JSC is installed from npm
url("$rootDir/../node_modules/jsc-android/dist")
}
maven { url 'https://www.jitpack.io' }
google()
}
}
additionally android > app > build.gradle
apply plugin: "com.android.application"
import com.android.build.OutputFile
project.ext.react = [
enableHermes: false, // clean and rebuild if changing
]
apply from: '../../node_modules/react-native-unimodules/gradle.groovy' // expo unimodules
apply from: "./env.gradle"
apply from: "../../node_modules/react-native/react.gradle"
apply from: "../../node_modules/react-native-vector-icons/fonts.gradle"
def enableSeparateBuildPerCPUArchitecture = false
def enableProguardInReleaseBuilds = false
def jscFlavor = 'org.webkit:android-jsc:+'
def enableHermes = project.ext.react.get("enableHermes", false);
android {
ndkVersion rootProject.ext.ndkVersion
compileSdkVersion rootProject.ext.compileSdkVersion
signingConfigs { // must come before defaultConfig since defaultConfig references it.
release {
keyAlias = 'totally'
storePassword 'real'
keyPassword 'secret'
storeFile file('stuff.keystore')
}
debug {
keyAlias = 'more'
storePassword 'super'
keyPassword 'secret'
storeFile file('stuff.keystore')
}
}
defaultConfig {
applicationId "com.redacted.someid"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 6
versionName "1.3.0"
signingConfig signingConfigs.release
}
splits {
abi {
reset()
enable enableSeparateBuildPerCPUArchitecture
universalApk false // If true, also generate a universal APK
include "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
}
}
buildTypes {
release {
minifyEnabled enableProguardInReleaseBuilds
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
signingConfig signingConfigs.release
}
debug {
signingConfig signingConfigs.debug
}
}
packagingOptions {
pickFirst "lib/armeabi-v7a/libc++_shared.so"
pickFirst "lib/arm64-v8a/libc++_shared.so"
pickFirst "lib/x86/libc++_shared.so"
pickFirst "lib/x86_64/libc++_shared.so"
}
// applicationVariants are e.g. debug, release
applicationVariants.all { variant ->
variant.outputs.each { output ->
// For each separate APK per architecture, set a unique version code as described here:
// https://developer.android.com/studio/build/configure-apk-splits.html
def versionCodes = ["armeabi-v7a": 1, "x86": 2, "arm64-v8a": 3, "x86_64": 4]
def abi = output.getFilter(OutputFile.ABI)
if (abi != null) { // null for the universal-debug, universal-release variants
output.versionCodeOverride =
defaultConfig.versionCode * 1000 + versionCodes.get(abi)
}
}
}
dexOptions {
javaMaxHeapSize "4g"
}
}
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
//noinspection GradleDynamicVersion
implementation "com.facebook.react:react-native:+" // From node_modules
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") {
exclude group:'com.facebook.fbjni'
}
debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") {
exclude group:'com.facebook.flipper'
exclude group:'com.squareup.okhttp3', module:'okhttp'
}
debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}") {
exclude group:'com.facebook.flipper'
}
implementation 'androidx.appcompat:appcompat:1.1.0'
addUnimodulesDependencies() // expo unimodules
if (enableHermes) {
def hermesPath = "../../node_modules/hermes-engine/android/";
debugImplementation files(hermesPath + "hermes-debug.aar")
releaseImplementation files(hermesPath + "hermes-release.aar")
} else {
implementation jscFlavor
}
}
task copyDownloadableDepsToLibs(type: Copy) {
from configurations.implementation
into 'libs'
}
project.ext.vectoricons = [
iconFontNames: [ 'MaterialIcons.ttf' ] // Name of the font files you want to copy
]
apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)
apply plugin: 'com.google.gms.google-services'
and of course package.json
{
"name": "redacted",
"version": "0.0.1",
"private": true,
"scripts": {
"postinstall": "patch-package && npx jetify",
"android": "react-native run-android",
"ios": "react-native run-ios",
"start": "react-native start",
"test": "jest",
"typescriptcheck": "tsc --noEmit --skipLibCheck"
},
"engines": {
"npm": "^6.14.12"
},
"dependencies": {
"@react-native-async-storage/async-storage": "^1.15.8",
"@react-native-community/async-storage": "^1.12.1",
"@react-native-community/datetimepicker": "^3.0.4",
"@react-native-community/masked-view": "0.1.11",
"@react-native-community/progress-bar-android": "^1.0.3",
"@react-native-community/progress-view": "^1.2.2",
"@react-native-community/push-notification-ios": "1.8.0",
"@react-native-firebase/app": "10.4.0",
"@react-native-firebase/messaging": "10.4.0",
"@react-navigation/core": "5.15.3",
"@react-navigation/drawer": "5.12.5",
"@react-navigation/native": "5.9.4",
"@react-navigation/routers": "5.7.2",
"@react-navigation/stack": "5.14.5",
"@types/react-native-app-link": "^1.0.0",
"autolinker": "^3.11.1",
"crypto-js": "^3.1.9-1",
"expo-constants": "^11.0.2",
"expo-screen-capture": "^3.2.0",
"jail-monkey": "^2.3.3",
"jetifier": "^1.6.5",
"moment": "^2.24.0",
"moment-timezone": "^0.5.27",
"patch-package": "^6.2.2",
"react": "17.0.2",
"react-native": "0.65.1",
"react-native-android-open-settings": "^1.3.0",
"react-native-app-link": "^1.0.0",
"react-native-autoheight-webview": "^1.5.4",
"react-native-bootsplash": "^3.1.5",
"react-native-custom-tabs": "^0.1.7",
"react-native-device-info": "^8.0.2",
"react-native-fingerprint-scanner": "^5.0.0",
"react-native-gesture-handler": "1.10.3",
"react-native-keychain": "^4.0.5",
"react-native-localize": "^2.0.2",
"react-native-modal-datetime-picker": "^9.0.0",
"react-native-pdf": "6.2.0",
"react-native-phone-call": "^1.0.9",
"react-native-picker-select": "^6.4.0",
"react-native-reanimated": "1.13.3",
"react-native-safari-view": "^2.1.0",
"react-native-safe-area-context": "3.2.0",
"react-native-screens": "3.4.0",
"react-native-svg": "^12.1.0",
"react-native-unimodules": "^0.14.8",
"react-native-vector-icons": "^7.0.0",
"react-native-webview": "^10.9.2",
"react-redux": "^7.2.0",
"redux": "^4.0.5",
"rn-fetch-blob": "0.12.0",
"rxjs": "^6.5.4",
"tslint": "^5.12.1",
"typescript": "^4.2.2",
"unimodules-barcode-scanner-interface": "^6.1.0",
"unimodules-camera-interface": "^6.1.0",
"unimodules-constants-interface": "^6.1.0",
"unimodules-face-detector-interface": "^6.1.0",
"unimodules-file-system-interface": "^6.1.0",
"unimodules-font-interface": "^6.1.0",
"unimodules-image-loader-interface": "^6.1.0",
"unimodules-permissions-interface": "^6.1.0",
"unimodules-sensors-interface": "^6.1.0",
"uuid": "^3.3.3"
},
"devDependencies": {
"@babel/core": "^7.12.9",
"@babel/plugin-transform-strict-mode": "^7.10.4",
"@babel/runtime": "^7.12.5",
"@types/crypto-js": "^3.1.43",
"@types/jest": "25.1.0",
"@types/moment-timezone": "^0.5.12",
"@types/react": "^16.9.43",
"@types/react-native": "^0.63.2",
"@types/react-native-custom-tabs": "^0.1.1",
"@types/react-native-safari-view": "^2.0.4",
"@types/react-native-vector-icons": "^6.4.5",
"@types/react-redux": "^7.1.9",
"@types/react-test-renderer": "^16.9.2",
"@types/remote-redux-devtools": "^0.5.3",
"@types/source-map": "^0.5.7",
"@types/uuid": "^3.4.6",
"babel-jest": "^26.6.3",
"babel-plugin-module-resolver": "^3.2.0",
"jest": "^26.6.3",
"jest-transform-stub": "^2.0.0",
"metro-react-native-babel-preset": "^0.66.0",
"react-native-codegen": "^0.0.7",
"react-native-dotenv": "^0.2.0",
"react-native-typescript-transformer": "^1.2.13",
"react-test-renderer": "17.0.2",
"remote-redux-devtools": "^0.5.16",
"source-map": "^0.7.3",
"ts-jest": "25.1.0"
},
"optionalDependencies": {
"fbjs": "1.0.0",
"fsevents": "1.2.9"
},
"jest": {
"verbose": true,
"preset": "react-native",
"moduleFileExtensions": [
"ts",
"tsx",
"js"
],
"transform": {
"^.+\\.(js)$": "<rootDir>/node_modules/react-native/jest/preprocessor.js",
"^.+\\.(ts|tsx)$": "ts-jest"
},
"testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$",
"testPathIgnorePatterns": [
"\\.snap$",
"<rootDir>/node_modules/",
"<rootDir>/ios/",
"<rootDir>/.history/"
],
"cacheDirectory": ".jest/cache",
"moduleNameMapper": {
"^.+.(png)$": "jest-transform-stub"
},
"setupFiles": [
"<rootDir>/jest.setup.js"
]
},
"react-native-unimodules": {
"android": {
"exclude": [
"expo-file-system",
"expo-image-loader",
"expo-permissions"
]
},
"ios": {
"exclude": [
"expo-file-system",
"expo-image-loader",
"expo-permissions"
]
}
}
}
*********** Update **********
The problem was related to processing .env and .env.production files. Although the below code was fine in previous iterations, it was throwing the resValue error in this version of gradle. (see loop processing variants, it attempts to set variant.resValue)
android > app > env.gradle
dependencies {
implementation 'org.apache.commons:commons-text:1.6'
}
tasks.whenTaskAdded { task ->
if (task.name == 'preDebugBuild') {
task.doFirst {
buildResourceValues('debug')
}
} else if (task.name == 'preReleaseBuild') {
task.doFirst {
buildResourceValues('release')
}
}
}
def buildResourceValues(buildVariant) {
def env = readEnvFile(buildVariant)
android.applicationVariants.all { variant ->
if (variant.name.indexOf(buildVariant) > -1) {
env.each { key, val ->
variant.resValue 'string', key, escapeResourceValue(val)
variant.buildConfigField 'String', key, "\"${groovy.json.StringEscapeUtils.escapeJava(val)}\""
}
}
}
}
def readEnvFile(buildVariant) {
def env = [:]
def envFile = '.env'
if (buildVariant != 'debug') {
envFile = '.env.production'
}
File file = new File("$project.rootDir/../$envFile")
if (file.exists()) {
file.eachLine { line ->
if (line[0] != '#' && line.indexOf('=') > -1) {
def split = line.indexOf('=')
def key = line.substring(0, split)
def val = line.substring(split + 1, line.length())
env.put(key, val)
}
}
}
return env
}
def static escapeResourceValue(str) {
// & and < are automatically escaped, but other chars need to be manually escaped
def escapes = [
"@": "\\@",
"?": "\\?",
"%": "%%",
"\'": "\\'",
"\"": "\\\"",
"\\": "\\\\"
]
def retString = str.replaceAll(/[@?%'"\\]/) {match ->
return escapes[match[0]]
}
return retString
}