Detox: Android build fails for libs with minSdkVersion 17 with react-native 0.64

Describe the bug

The following command fails after upgrading to RN 0.64

./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug

With the following message (only this library seems to fail).

> Task :react-native-pager-view:processDebugAndroidTestManifest FAILED
[androidx.vectordrawable:vectordrawable-animated:1.0.0] /.gradle/caches/transforms-2/files-2.1/3050775700026023c1db3db79ab19a97/vectordrawable-animated-1.0.0/AndroidManifest.xml Warning:
        Package name 'androidx.vectordrawable' used in: androidx.vectordrawable:vectordrawable-animated:1.0.0, androidx.vectordrawable:vectordrawable:1.0.1.
/node_modules/react-native-pager-view/android/build/intermediates/tmp/manifest/androidTest/debug/manifestMerger2807704319365703146.xml:5:5-74 Error:
        uses-sdk:minSdkVersion 17 cannot be smaller than version 21 declared in library [com.facebook.react:react-native:0.64.0] /.gradle/caches/transforms-2/files-2.1/4e32a44ca1590525038237dd6b47cd32/jetified-react-native-0.64.0/AndroidManifest.xml as the library might be using APIs not available in 17
        Suggestion: use a compatible library with a minSdk of at most 17,
                or increase this project's minSdk version to at least 21,
                or use tools:overrideLibrary="com.facebook.react" to force usage (may lead to runtime failures)

See http://g.co/androidstudio/manifest-merger for more information about the manifest merger.

Running ./gradlew assembleDebug or ./gradlew bundleRelease then npx react-native run-android --variant=release works fine.

Maybe this issue should be posted in the react-native-pager-view repo but as I can only reproduce when running the detox build command I’m asking here first.

Steps To Reproduce

  • I have tested this issue on the latest Detox release and it still reproduces

Expected behavior

Build should not fail with this error.

Detox Trace-Logs

NA

Device logs (adb logcat)

NA

Environment (please complete the following information):

  • Detox: 18.9.0
  • React Native: 0.64.0
  • Node: v14.8.0
  • Device: NA
  • OS: KDE neon User Edition 5.21
  • Test-runner (select one): jest-circus

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Reactions: 15
  • Comments: 38 (10 by maintainers)

Commits related to this issue

Most upvoted comments

Had the same problem, I was able to fix it by adding this in android/build.gradle:

subprojects { 
    ext {
        compileSdk = rootProject.ext.compileSdkVersion
        minSdk = rootProject.ext.minSdkVersion
        targetSdk = rootProject.ext.targetSdkVersion
    }
    afterEvaluate { subproject ->
        if((subproject.plugins.hasPlugin('android') || subproject.plugins.hasPlugin('android-library'))) {
            android {
                compileSdkVersion rootProject.ext.compileSdkVersion
                buildToolsVersion rootProject.ext.buildToolsVersion
                defaultConfig {
                    minSdkVersion rootProject.ext.minSdkVersion
                    targetSdkVersion rootProject.ext.targetSdkVersion
                }
            }
        }
    }
}

Reference: https://github.com/uxcam/react-native-ux-cam/issues/24#issuecomment-892547229

@josephbaylon The library needs to be fixed so that

minSdkVersion 16

(or similar) in android/build.gradle is replaced with with

minSdkVersion rootProject.hasProperty('minSdkVersion') ? rootProject.minSdkVersion : 16

You have three options:

  1. Create a bug report / PR and wait for the fix to be released
  2. Create a fork of the library and reference it in package.json using an https/github URL
  3. Patch it during installation using patch-package

I’m still seem to be facing this issue. "detox": "^18.15.0",

buildscript {
    ext {
        buildToolsVersion = "29.0.3"
        minSdkVersion = 21
        compileSdkVersion = 29
        targetSdkVersion = 29
        ndkVersion = "20.1.5948944"
        kotlinVersion = "1.3.61"
    }
    repositories {
        google()
        jcenter()
    }
    ext.kotlinVersion = '1.3.61' // (check what the latest version is!)
    dependencies {
        classpath("com.android.tools.build:gradle:4.1.0")
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}
> Task :appcenter:processDebugAndroidTestManifest FAILED
[androidx.vectordrawable:vectordrawable-animated:1.0.0] /Users/mkelly/.gradle/caches/transforms-2/files-2.1/7db194964415ec513e75fcad1bfcf177/vectordrawable-animated-1.0.0/AndroidManifest.xml Warning:
        Package name 'androidx.vectordrawable' used in: androidx.vectordrawable:vectordrawable-animated:1.0.0, androidx.vectordrawable:vectordrawable:1.0.1.
/Users/mkelly/aLifeLivedReactNative/node_modules/appcenter/android/build/intermediates/tmp/manifest/androidTest/debug/manifestMerger8791020369522696725.xml:5:5-74 Error:
        uses-sdk:minSdkVersion 16 cannot be smaller than version 21 declared in library [com.facebook.react:react-native:0.64.0] /Users/mkelly/.gradle/caches/transforms-2/files-2.1/3c51cfc9aca591c4cb5239f8f9999dec/jetified-react-native-0.64.0/AndroidManifest.xml as the library might be using APIs not available in 16
        Suggestion: use a compatible library with a minSdk of at most 16,
                or increase this project's minSdk version to at least 21,
                or use tools:overrideLibrary="com.facebook.react" to force usage (may lead to runtime failures)

See http://g.co/androidstudio/manifest-merger for more information about the manifest merger.

ANY LIBRARY CAN HAVE ANY minSdkVersion! It’s not unique to this library. And if the libraries move to 21, then how would all the people still using react-native 0.63 to support API16 feel?

It’s not a bug.

TAKE CONTROL OF YOUR DEPENDENCIES by using gradle to raise them during the build.

Please don’t spam other modules with requests to pass things through. There is no way the whole ecosystem will support this in a timely manner, and there is a much cleaner way to do this:

take control of your own dependencies using buitt-in gradle features

Here’s what I mean

In your android/build.gradle put these lines:

subprojects {
  task listAllDependencies(type: DependencyReportTask) {}


  // Force all subprojects to use one and only one set of versions
  ext {
    compileSdk = rootProject.ext.compileSdkVersion
    buildTools = rootProject.ext.buildToolsVersion
    minSdk = rootProject.ext.minSdkVersion
    targetSdk = rootProject.ext.targetSdkVersion
  }
  afterEvaluate { project ->
    if (!project.name.equalsIgnoreCase("app")
      && project.hasProperty("android")) {
      android {
        compileSdkVersion compileSdk
        buildToolsVersion buildTools
        defaultConfig {
          minSdkVersion minSdk
          targetSdkVersion targetSdk
        }
      }
    }

Example taken from a project that is open source and uses both detox and react-native 0.64.2, you can see it in action

I tried this command and the build passed

cd android ; ./gradlew app:assembleRelease app:assembleAndroidTest -DtestBuildType=release ; cd -

Do the same for debug build. Add ‘app’ before assembleRelease and assembleAndroidTest

It’s quite interesting suggestion. In my team, we had a little discussion about its safety. We came up with that trade-off solution:

subprojects { project ->
  afterEvaluate {
        // Added to Fix Detox build command for libs with minSdk < required one.
        if (gradle.startParameter.taskNames.contains("assembleDebug")) {
          defaultConfig {
            minSdkVersion rootProject.ext.minSdkVersion
          }
        }
      }
}

You might ask why? Just to be sure that these older modules still have their versions during building a release type.

@mikehardy That’s a neat workaround. (Haven’t tested it myself though.)

Still, it’s best practice that libraries pass through minSdkVersion. Every module template / generator I’ve seen does it. It’s the way things “just work” instead of having to have deep knowledge of gradle. I recommend sending PR’s to projects to make them better. It’s a minority of modules that don’t have pass-through already.

In the interim I already suggested either forking or patch-package’ing the module. Gradle override is fine as well.

@mikehardy I still would expect an explanation why the default release build works correctly (in contrast to detox build)? All I am saying is that the no solution is the best solution. I believe lots of people will lose time and struggle on this.


Anyways I opted in using @mikehardy `s gradle solution. At first it was not working, so I decided to investigate the repository in which he said it worked. I noticed I had some different versions to some dependencies. I went down the rabbit hole to update starting from gradle. In the end I am not sure what fixed it, but I am mostly certain it was either gradle or react-native update. Here is a diff of what changes I needed to make:

diff --git a/android/app/build.gradle b/android/app/build.gradle
index 61b1580..44d943d 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -1,3 +1,4 @@
+apply plugin: 'com.android.application'
 apply plugin: "com.github.triplet.play"
 apply plugin: "com.android.application"
 apply plugin: 'com.google.gms.google-services'
diff --git a/android/build.gradle b/android/build.gradle
index 0afd430..7cb3ca0 100755
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -2,7 +2,6 @@
 
 buildscript {
     ext {
-        buildToolsVersion = "29.0.3"
         minSdkVersion = 21
         compileSdkVersion = 29
         targetSdkVersion = 29
@@ -19,8 +18,8 @@ buildscript {
         mavenCentral()
     }
     dependencies {
-        classpath "com.github.triplet.gradle:play-publisher:3.4.0"
-        classpath "com.android.tools.build:gradle:4.1.0"
+        classpath "com.github.triplet.gradle:play-publisher:3.4.0-agp4.2"
+        classpath "com.android.tools.build:gradle:4.2.1"
         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
         classpath 'com.google.gms:google-services:4.3.3'
         // NOTE: Do not place your application dependencies here; they belong
@@ -48,3 +47,24 @@ allprojects {
         maven { url 'https://www.jitpack.io' }
     }
 }
+
+subprojects {
+    // Force all subprojects to use one and only one set of versions
+    ext {
+        compileSdk = rootProject.ext.compileSdkVersion
+        minSdk = rootProject.ext.minSdkVersion
+        targetSdk = rootProject.ext.targetSdkVersion
+    }
+    afterEvaluate { project ->
+        if (!project.name.equalsIgnoreCase("app") && project.hasProperty("android")) {
+            android {
+                compileSdkVersion compileSdk
+                buildToolsVersion "30.0.2"
+                defaultConfig {
+                    minSdkVersion minSdk
+                    targetSdkVersion targetSdk
+                }
+            }
+        }
+    }
+}
diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties
index 14e30f7..1f3fdbc 100644
--- a/android/gradle/wrapper/gradle-wrapper.properties
+++ b/android/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,5 @@
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
index bba7a0c..23a3b97 100644
--- a/package.json
+++ b/package.json
@@ -33,7 +33,7 @@
     "@babel/register": "^7.9.0",
     "@babel/runtime": "^7.12.5",
-    "@react-native-community/async-storage": "^1.9.0",
+    "@react-native-async-storage/async-storage": "^1.15.5",
     "@react-native-community/google-signin": "^4.0.3",
     "@react-native-community/masked-view": "^0.1.7",
     "@react-native-community/netinfo": "^5.9.4",
@@ -71,7 +71,7 @@
     "react": "17.0.1",
     "react-dom": "^16.13.1",
     "react-intl": "^4.7.6",
-    "react-native": "0.64.0",
+    "react-native": "0.64.2",
     "react-native-calendars": "^1.265.0",
     "react-native-device-info": "^5.5.7",
     "react-native-dotenv": "^0.2.0",
@@ -98,14 +98,14 @@
   "devDependencies": {
     "@react-native-community/eslint-config": "^2.0.0",
     "@types/chai": "^4.2.11",
-    "@types/detox": "^16.4.1",
+    "@types/detox": "^17.14.0",
     "@types/mocha": "^8.0.3",
     "@types/react-test-renderer": "17.0.1",
     "@typescript-eslint/eslint-plugin": "^2.23.0",
     "@typescript-eslint/parser": "^2.23.0",
     "better-npm-audit": "^1.1.1",
     "chalk": "^4.0.0",
-    "detox": "^17.4.5",
+    "detox": "^18.18.1",
     "dotenv": "^8.2.0",
     "eslint": "7.14.0",
     "eslint-config-prettier": "^6.10.0",
diff --git a/src/Localization/LocalizationProvider.tsx b/src/Localization/LocalizationProvider.tsx
index e6766f3..0328fcc 100644
--- a/src/Localization/LocalizationProvider.tsx
+++ b/src/Localization/LocalizationProvider.tsx
@@ -1,5 +1,5 @@
 import React, { useState, FunctionComponent, useCallback } from 'react';
-import AsyncStorage from '@react-native-community/async-storage';
+import AsyncStorage from '@react-native-async-storage/async-storage';
 import { DEFAULT_LANGUAGE, languages } from './Translations';
 import * as RNLocalize from 'react-native-localize';
 import LanguageSetContext from './LanguageSetterContext';
diff --git a/test/integration/mocha/init.js b/test/integration/mocha/init.js
index 29a4d98..6fb10d9 100644
--- a/test/integration/mocha/init.js
+++ b/test/integration/mocha/init.js
@@ -25,6 +25,7 @@ before(async () => {
     await detox.init(config);
     global.isDetoxInitialized = true;
   }
+  await device.launchApp();
 });
 
 beforeEach(async function () {

After much digging around I figured out that rootProject.ext.hasProperty always returns null (bug report by me), which caused me a lot of confusion. It’s not documented to work, but it’s very confusing that it doesn’t throw an exception.

Essentially RN libraries that either have a hard-coded minSdkVersion or which use the incorrect rootProject.ext.hasProperty("minSdkVersion") will cause Detox builds to fail for anyone using RN 0.64. For me this was three libraries, react-native-webview (!), react-native-haptic-feedback and react-native-navigation-bar-color.

This will be a major painpoint for anyone using Detox on RN 0.64. Still no idea what is different in the Detox build to cause it to fail.

subprojects { project ->
    afterEvaluate {
        if (gradle.startParameter.taskNames.contains("assembleAndroidTest")) {
            android {
                compileSdkVersion rootProject.ext.compileSdkVersion
                buildToolsVersion rootProject.ext.buildToolsVersion
                defaultConfig {
                    minSdkVersion rootProject.ext.minSdkVersion
                    targetSdkVersion rootProject.ext.targetSdkVersion
                }
            }
        }
    }
}

I’m not going to troubleshoot your project. I’ve linked one that works. Build off that.

@plaa for us the fix was to make sure that the library we were using (react-native-pager-view) had the right configuration (https://github.com/callstack/react-native-pager-view/pull/319/files#diff-197b190e4a3512994d2cebed8aff5479ff88e136b8cc7a4b148ec9c3945bd65a) in the android/build.gradle file.

Looking at the library you are using you are facing the same issue. In this file https://github.com/junina-de/react-native-haptic-feedback/blob/master/android/build.gradle, replace minSdkVersion 16 with rootProject.hasProperty('minSdkVersion') ? rootProject.minSdkVersion : 16

I’m wondering @Andarius (seen we’re using the same reps). Did you close this because you felt the issue was down to the dependencies?

What I’ve found interesting here is that:

  1. Detox builds in a different way to the actual build of the app (which works and runs fine).
  2. Despite the error the workarounds it suggests don’t work
    • the minSdkVersion defined in your project build.gradle seems to be ignored
    • Adding a <uses-sdk> in an AndroidManfiest.xml doesn’t seem to fix the issue either

We’re left with just hacking/trying to get libraries we’re relying upon updated. Which is one answer, but I don’t understand why Detox builds differently/these fixes don’t work.