React Native: match multiple Maven urls to specific dependencies?

Viewed 4180

Please don't mark this as duplicate. I've spent two days scouring Github, Stack, Android Studio docs, RN docs, and Gradle docs looking for an answer.

The Problem

Our React Native project won't build on Android. One build error is

Could not find com.github.wix-playground:ahbottomnavigation:2.4.9

The fix for this error is to add maven { url 'https://jitpack.io' } to the project build.gradle

After adding this, however, we get a second error, which is

Execution failed for task ':react-native-ble-plx:compileDebugJavaWithJavac'

The fix for this is adding maven { url 'https://maven.google.com' } to build.gradle

But after adding this second fix, we trigger the first error again.

The Question

Is there a way to list both maven { <url> }s in build.gradle in such a way the each maven { <url> } is used only for the specific dependency relying on it?

OR

Can we force the same support library version across all dependencies?

  • I have tried numerous methods from these Gradle Docs
  • I've tried the answer in this Stack Post
  • I've updated to Android Studio 3.4.1

Any help would be greatly appreciated.

Our Files

Project level build.gradle

buildscript {
    repositories {
        google()
        mavenLocal()
        mavenCentral()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.4.1'
    }
}

allprojects {
    repositories {
        google()
        jcenter()
        mavenCentral()
        mavenLocal()
        // maven { url 'https://maven.google.com' }
        maven { url 'https://jitpack.io' }     
        maven {
          url "$rootDir/../node_modules/react-native/android" // This URL still works
        }
    }
}
  ext {
        buildToolsVersion = "28.0.3"
        minSdkVersion = 19
        compileSdkVersion = 26
        targetSdkVersion = 26
        supportLibVersion = "28.0.0"
    }

    subprojects { subproject ->
    afterEvaluate {
        if ((subproject.plugins.hasPlugin('android') || subproject.plugins.hasPlugin('android-library'))) {
            android {
                variantFilter { variant ->
                    def names = variant.flavors*.name
                    if (names.contains("reactNative51") || names.contains("reactNative56")) {
                        setIgnore(true)
                    }
                }
            }
        }
    }
}

app/build.gradle

buildscript {
    repositories {
        maven { url 'https://maven.fabric.io/public' }
    }
    dependencies {
        classpath 'io.fabric.tools:gradle:1.+'
    }
}

apply plugin: "com.android.application"
apply plugin: "io.fabric"
apply from: project(':react-native-config').projectDir.getPath() + "/dotenv.gradle"
apply from: "../../node_modules/react-native-vector-icons/fonts.gradle"

repositories {
    maven { url 'https://maven.fabric.io/public' }
}

import com.android.build.OutputFile

project.ext.react = [
    entryFile: "index.js",
    bundleInStaging: true,
    devDisabledInStaging: true,
    inputExcludes: ["ios/**", "__tests__/**", "bundle_out/**"]
]

apply from: "../../node_modules/react-native/react.gradle"
apply from: "../../node_modules/react-native-sentry/sentry.gradle"

def enableSeparateBuildPerCPUArchitecture = false

def enableProguardInReleaseBuilds = false

def debugKeystorePropertiesFile = rootProject.file("keystores/debug.keystore.properties");
def keystoreProperties = new Properties()
keystoreProperties.load(new FileInputStream(debugKeystorePropertiesFile))

def releaseKeystorePropertiesFile = rootProject.file("keystores/release.keystore.properties");
def releaseKeystoreProperties = new Properties()
releaseKeystoreProperties.load(new FileInputStream(releaseKeystorePropertiesFile))

android {
    compileSdkVersion 28
    buildToolsVersion '28.0.3'

    defaultConfig {
        applicationId "com.example"
        missingDimensionStrategy "RNN.reactNativeVersion", "reactNative55"
        minSdkVersion 21
        targetSdkVersion 28
        versionCode 177
        versionName "2.0.4"
        multiDexEnabled true
        ndk {
            abiFilters "armeabi-v7a", "x86"
        }
        manifestPlaceholders = [
            FABRIC_API_KEY: project.env.get("FABRIC_API_KEY"),
            FABRIC_SECRET: project.env.get("FABRIC_SECRET")
        ]
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    signingConfigs {
        debug {
            storeFile file(keystoreProperties['key.store'])
            storePassword keystoreProperties['key.store.password']
            keyAlias keystoreProperties['key.alias']
            keyPassword keystoreProperties['key.alias.password']
        }
        release {
            storeFile file(releaseKeystoreProperties['key.store'])
            storePassword releaseKeystoreProperties['key.store.password']
            keyAlias releaseKeystoreProperties['key.alias']
            keyPassword releaseKeystoreProperties['key.alias.password']
        }
    }
    splits {
        abi {
            reset()
            enable enableSeparateBuildPerCPUArchitecture
            universalApk false  // If true, also generate a universal APK
            include "armeabi-v7a", "x86"
        }
    }
    buildTypes {
        release {
            signingConfig signingConfigs.release
            minifyEnabled enableProguardInReleaseBuilds
            proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro", "proguard-devsupport.pro"
            testProguardFile 'proguard-debug.pro'
        }
        staging {
            signingConfig signingConfigs.debug
            matchingFallbacks = ['release', 'debug']
        }
        debug {
            signingConfig signingConfigs.debug
        }
    }
    // applicationVariants are e.g. debug, release
    applicationVariants.all { variant ->
        variant.outputs.each { output ->
            def versionCodes = ["armeabi-v7a":1, "x86":2]
            def abi = output.getFilter(OutputFile.ABI)
            if (abi != null) {  // null for the universal-debug, universal-release variants
                output.versionCodeOverride =
                        versionCodes.get(abi) * 1048576 + defaultConfig.versionCode
            }
        }
    }
}

configurations.all {
    resolutionStrategy.eachDependency { DependencyResolveDetails details ->
        def requested = details.requested
        if (requested.group == 'com.android.support' && requested.name != 'multidex') {
            details.useVersion "${rootProject.ext.supportLibVersion}"
        }
    }
}

dependencies {
    compile('com.google.android.gms:play-services-gcm:11.8.0') {
        force = true
    }
    compile project(':react-native-push-notification')
    implementation project(':react-native-awesome-card-io')
    implementation project(':react-native-fabric')
    implementation project(':react-native-randombytes')
    implementation project(':react-native-linear-gradient')
    implementation project(':react-native-spinkit')
    implementation project(':react-native-keychain')
    implementation project(':react-native-vector-icons')
    implementation project(':react-native-ble-plx')
    implementation project(':react-native-config')
    implementation project(':react-native-sentry')
    implementation project(':react-native-device-info')
    implementation fileTree(dir: "libs", include: ["*.jar"])
    implementation 'androidx.appcompat:appcompat:1.0.0-alpha3'
    implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}"
    implementation "com.android.support:appcompat-v7:27.1.0"
    implementation 'com.android.support:design:27.1.0'
    implementation "com.facebook.react:react-native:+"  // From node_modules
    implementation('com.crashlytics.sdk.android:crashlytics:2.9.3@aar') {
        transitive = true;
    }
    implementation project(':react-native-navigation')
    implementation project(':react-native-tcp')
    implementation 'com.android.support:design:25.4.0'
    implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}"
    implementation 'com.android.support:multidex:1.0.3'
    implementation ('com.github.wix-playground:ahbottomnavigation:2.4.9') {
        exclude group: "com.android.support"
    }
}

task copyDownloadableDepsToLibs(type: Copy) {
    from configurations.compile
    into 'libs'
}

AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
    <uses-permission android:name="android.permission.BLUETOOTH"/>
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission-sdk-23 android:name="android.permission.ACCESS_COARSE_LOCATION"/>
    <uses-permission-sdk-23 android:name="android.permission.ACCESS_FINE_LOCATION"/>
    <!-- < Only if you're using GCM or localNotificationSchedule() > -->
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <permission
        android:name="${applicationId}.permission.C2D_MESSAGE"
        android:protectionLevel="signature" />
    <uses-permission android:name="${applicationId}.permission.C2D_MESSAGE" />
    <!-- < Only if you're using GCM or localNotificationSchedule() > -->
    <uses-permission android:name="android.permission.VIBRATE" />
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>

    <application
      android:name="android.support.multidex.MultiDexApplication"
      android:label="@string/app_name"
      android:icon="@mipmap/ic_launcher"
      android:allowBackup="false"
      android:fullBackupContent="false"
      android:theme="@style/AppTheme">

        <meta-data  android:name="com.dieam.reactnativepushnotification.notification_channel_name"
            android:value="YOUR NOTIFICATION CHANNEL NAME"/>
        <meta-data  android:name="com.dieam.reactnativepushnotification.notification_channel_description"
            android:value="YOUR NOTIFICATION CHANNEL DESCRIPTION"/>
        <!-- Change the resource name to your App's accent color - or any other color you want -->
        <meta-data  android:name="com.dieam.reactnativepushnotification.notification_color"
            android:resource="@android:color/white"/>

        <!-- < Only if you're using GCM or localNotificationSchedule() > -->
        <receiver
            android:name="com.google.android.gms.gcm.GcmReceiver"
            android:exported="true"
            android:permission="com.google.android.c2dm.permission.SEND" >
            <intent-filter>
                <action android:name="com.google.android.c2dm.intent.RECEIVE" />
                <category android:name="${applicationId}" />
            </intent-filter>
        </receiver>
        <!-- < Only if you're using GCM or localNotificationSchedule() > -->

        <receiver android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationPublisher" />
        <receiver android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationBootEventReceiver">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
            </intent-filter>
        </receiver>
        <service android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationRegistrationService"/>

        <!-- < Only if you're using GCM or localNotificationSchedule() > -->
        <service
            android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationListenerServiceGcm"
            android:exported="false" >
            <intent-filter>
                <action android:name="com.google.android.c2dm.intent.RECEIVE" />
            </intent-filter>
        </service>

      <activity
        android:name=".MainActivity"
        android:label="@string/app_name"
        android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
        android:screenOrientation="portrait"
        android:windowSoftInputMode="adjustPan">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
      </activity>
      <meta-data
        android:name="io.fabric.ApiKey"
        android:value="${FABRIC_API_KEY}"
      />
      <meta-data
        android:name="io.fabric.ApiSecret"
        android:value="${FABRIC_SECRET}"
       />
      <activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
    </application>

</manifest>
3 Answers

Is there a way to list both maven { }s in build.gradle in such a way the each maven { } is used only for the specific dependency relying on it?

Matching repositories to dependencies is an incubating feature.

Currently you can check the official doc about defining multiple repo:

You can define more than one repository for resolving dependencies. Declaring multiple repositories is helpful if some dependencies are only available in one repository but not the other.

The order of declaration determines how Gradle will check for dependencies at runtime. If Gradle finds a module descriptor in a particular repository, it will attempt to download all of the artifacts for that module from the same repository. You can learn more about the inner workings of Gradle’s resolution mechanism.

Also

Can we force the same support library version across all dependencies?

You can:

  • Exclude the conflicted module/library from one of the dependencies
  • explicitly declare the support library version

ex: ex:

  implementation "your dependency:$version" {
       exclude group: "org.xxxx.xxx", module: "xxxxxxx"
   }

ex:

android {
    configurations.all {
        resolutionStrategy.force 'com.android.support:support-xxxx:XX.YY.ZZ'
    }
}

Pay attention to this approach because you could going to force the use of a backward version in some libraries.

We used the resolution strategy (see comment above) to fix our issue. Also, in our app/build.gradle we decided against using androidx support library implementations and instead used the older com.android.support:xxx libraries. In the end the solution looked something like this.

app/build.gradle

// Force the same support library version across all dependencies
configurations.all {
    resolutionStrategy.eachDependency { DependencyResolveDetails details ->
        def requested = details.requested
        if (requested.group == 'com.android.support' && requested.name != 'multidex') {
            details.useVersion "${rootProject.ext.supportLibVersion}"
        }
    }
}

implementation 'com.android.support:design:25.4.0'
implementation 'com.android.support:appcompatv7:${rootProject.ext.supportLibVersion}'

using this add multiple dependency at a single command

yarn add react-native-gesture-handler react-native-modal-dropdown react-native-vector-icons react-navigation



npm install --save react-native-gesture-handler react-native-modal-dropdown react-native-vector-icons react-navigation

as well use react native version wise

react-native init --version="react-native@0.36.0" MyNewApp

react-native init --version="react-native@0.59.8" MyNewAppName

Related