How to limit max version of a transitive dependency - android

I have a problem about how to specify the max version for a transitive dependency. Let me explain this with an example:
My app uses the library library-foo, the problem is that library-foo version 3.0+ introduces breaking changes and my app is not ready yet. So I declare it in my dependencies:
implementation 'com.example:library-foo:(,3.0)'
My problem comes when I introduce a new dependency, lets say library-bar. That library introduces a transitive dependency on library-foo
library-bar 5.0 -depends on-> library-foo 2.2
library-bar 5.5 -depends on-> library-foo 3.1
So, after adding the new dependency:
implementation 'com.example:library-foo:(,3.0)'
implementation 'com.example:library-bar:5.+'
I was hopping the dependency resolution to be smart enough. Gradle included the 5.5 version, introducing the breaking changes in my project.
I know I could also limit the max version of library-bar but in projects with a lot of dependencies, it would be great to declare my max version of the troublesome library (foo in this case) forcing the rest of dependencies to be adapted to this.
Is it possible to do that?

You can use gradle's constraints:
implementation 'com.example:library-foo:3.0'
implementation 'com.example:library-bar:5.+'
constraints {
implementation('com.example:library-foo:3.0') {
because 'Versions greater than 3.0 introduce bugs'
}
}

Related

Android Gradle - transitive dependencies take precedence (upgrade) the declared (first-level) one

What I have in my build.gradle:
implementation "androidx.recyclerview:recyclerview:1.1.0"
implementation 'com.github.kirich1409:viewbindingpropertydelegate-noreflection:1.5.6'
The latter dependency has its own RecyclerView transitive dependency:
implementation "androidx.recyclerview:recyclerview:1.2.1"
What I see then in the dependency tree is (for the RecyclerView dependency I declared):
+--- androidx.recyclerview:recyclerview:1.1.0 -> 1.2.1
Also, in the code, I got the getBindingAdapterPosition method, which appeared after 1.1.0.
Certainly, we should always try to use the latest version of the dependencies, but is it the correct behavior?!
I thought that transitive dependencies wouldn't override the declared ones.
What if my code relies on some functionality present in version 1.1.0 only?
The default behaviour is described in the doc:
Gradle resolves any dependency version conflicts by selecting the latest version found in the dependency graph.
You can enforce a certain version of a transitive dependency, excluding a dependency. Something like:
implementation('commons-beanutils:commons-beanutils:1.9.4') {
exclude group: 'commons-collections', module: 'commons-collections'
}
but please note that:
Excluding a dependency completely requires a conscious decision. Excluding a transitive dependency might lead to runtime errors if external libraries do not properly function without them. If you use excludes, make sure that you do not utilise any code path requiring the excluded dependency by sufficient test coverage.
You can also use a strict versions even if a transitive dependency says otherwise. Something like:
implementation('commons-codec:commons-codec') {
version {
strictly '1.9'
}
}
Also in this case note that:
Forcing a version of a dependency requires a conscious decision. Changing the version of a transitive dependency might lead to runtime errors if external libraries do not properly function without them.

Migrating support to Android X: Are versions consistent across Modules?

I am migrating a big project to AndroidX.
On my former project, on the top build.gradle, I have defined a variable with the support library version so the 30+ child build.gradle's include the desired support version:
ext {
supportLibraryVersion = "28.0.0"
buildToolsVersion = "28.0.3"
googleServicesVersion = "15.0.0"
}
My question is, now that I have run the Android X refactoring, I see that dependencies have been converted to different packages under the Android X namespace and even out of it:
implementation "androidx.appcompat:appcompat:1.0.0'
implementation "androidx.percentlayout:percentlayout:1.0.0"
implementation 'androidx.palette:palette:1.0.0'
implementation 'com.google.android.material:material:1.0.0'
My question is if now with Android X all module versions are synchronized so it makes sense doing in the top gradle:
ext {
androidXLibraryVersion = "x.x.x"
}
My guess is that modules outside the Android X namespace will have their own version schedule, but I am not even sure that the modules under AndroidX namespace will use common versions.
What do you think?
In the AndroidX world, every group (i.e., the part before the first : such as androidx.appcompat) can have a separate version as per the AndroidX Overview:
Unlike the Support Library, AndroidX packages are separately maintained and updated. The androidx packages use strict Semantic Versioning starting with version 1.0.0. You can update AndroidX libraries in your project independently.
As seen on the AndroidX releases page, they absolutely do upgrade at different rates, but the semantic versioning requirement ensures that stable versions of the libraries will continue to work together.

Why must $kotlin_version be explicitly specified in Android?

Supporting Kotlin in an Android studio project requires two dependencies: kotlin-gradle-plugin in Project/build.gradle and kotlin-stdlib-jdk7 in Project/app/build.gradle, and these two need to have the same version. The common method seems to be using a single kotlin_version variable which you then have to manually change when the IDE updates its Kotlin plugin — as of Android Studio 3.1.3, the IDE is still not able to automatically update the dependencies if you use a $variable as the version.
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
In non-Android Kotlin projects i.e. those using apply: 'kotlin' instead of apply plugin: 'kotlin-android', it is possible to simply omit the version from the kotlin-stdlib-jdk7 dependency, which will then be automatically resolved from the plugin.
implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk7'
This even works on non-Android modules within Android projects. My question is, why is this not possible in Android modules? Why can't the $kotlin_version simply be omitted? If the feature has been present since Kotlin 1.1.2, why is it still causing compile errors on Android even on Kotlin 1.2.51? Or is it actually possible to do this, and if so, how can it be done?
In fact this is not Kotlin specific but has to do with how Gradle manages dependencies.
See https://docs.gradle.org/current/userguide/declaring_dependencies.html on how to specify the version of a dependency in Gradle.
In this case
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
is the standard way of setting the version. As the this version number is the same for several dependency it is declared in a variable in order to make it easy to change the version for all Kotlin libraries.
implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk7'
Uses dependency constraints which are available since Gradle 4.6. It is used for the same purpose. One can set the version of a library in a central place and this way keep the version at a common value without having to go through all gradle.properties files of a larger project.

Android support library incompatiblity

I have a jcenter library ToggleButtons I develop that I import into my app. After switching to support 26.1.0 in my app, I receive this error:
All com.android.support libraries must use the exact same version
specification (mixing versions can lead to runtime crashes). Found
versions 26.1.0, 25.3.1
ToggleButtons:
com.android.support:cardview-v7:25.3.1
Main app:
com.android.support:design:26.1.0
I'm using other libraries such as Glide that reference even earlier versions of the support library (I haven't upgraded to 4 yet), but those don't have an issue. Have I designed the library improperly somehow?
This was always a recommendation, now they're making it generate errors.
You absolutely can't run an app with both versions, because that would cause duplicated classes errors. That means you must pick one of those manually now, while previously gradle would automatically choose one for you.
I'd suggest you use the higher number, since doing the opposite risks missing new features/assets that either library or app really depends on.
You can add this between your android and dependencies blocks in your application / library module's build.gradle for each conflict you must manually solve:
def supportLibraryVersion = '26.0.1'
configurations.all {
resolutionStrategy {
force "com.android.support:cardview-v7:$supportLibraryVersion"
}
}
I guess you get the idea of how it works.
Edit:
As noted by #eugen-pechanec the best practice is having all your support libraries with same version throughout all your projects modules. Also, it's best to use the same numbers on build tools (in module's build.gradle, inside android block).
Here's what your app depends on:
+ design:26.1.0
+ appcompat-v7:26.1.0
+ support-v4:26.1.0
+ recyclerview-v7:26.1.0
+ support-v4:26.1.0
Here's what the library depends on:
+ cardview-v7:25.3.1 (i.e. at least 25.3.1)
Here's what it means:
Card view library doesn't have any (runtime) dependency on other support libraries so technically in this case it's safe to use different versions. However this may change at any time.
More importantly your own code does not define cardview-v7 as a dependency so there's no way for gradle to know it should pull updated version as well.
The easiest fix then is just defining the dependency in your build.gradle:
def supportLibraryVersion = '26.0.1'
compile "com.android.support:cardview-v7:$supportLibraryVersion"
No force, nothing special. Upgrading a dependency is not a problem. Only downgrading is.
I still don't understand why Glide doesn't throw this error when they're using support 25.
As hinted above, Glide uses at least support-v4 25.x.x. And because a newer version of support-v4 is already requested by your own module, the dependency gets silently upgraded.

Library dependency issues with gradle 3.0.0-alphaX

I have a library project with submodules that include many dependencies that I'd like to pass to the developer's application. For example, module A may include all the necessary appcompat dependencies.
With the migration changes, I've updated all compile cases to api, which should not affect anything. However, I no longer have access to any of the libraries dependencies. I can only use code and references from my library itself.
Is there any way around this?
One of the build gradle files of my library submodules can be found here for reference.
The dependencies:
dependencies {
api "org.jetbrains.kotlin:kotlin-stdlib:${KOTLIN}"
api "com.android.support:appcompat-v7:${ANDROID_SUPPORT_LIBS}"
api "com.android.support:support-v13:${ANDROID_SUPPORT_LIBS}"
api "com.android.support:design:${ANDROID_SUPPORT_LIBS}"
api "com.android.support:recyclerview-v7:${ANDROID_SUPPORT_LIBS}"
api "com.android.support:cardview-v7:${ANDROID_SUPPORT_LIBS}"
api "com.android.support.constraint:constraint-layout:${CONSTRAINT_LAYOUT}"
api "com.mikepenz:iconics-core:${ICONICS}#aar"
api "com.mikepenz:google-material-typeface:${IICON_GOOGLE}.original#aar"
api "com.afollestad.material-dialogs:core:${MATERIAL_DIALOG}"
api "com.jakewharton.timber:timber:${TIMBER}"
api "org.jetbrains.anko:anko-commons:${ANKO}"
}
Edit:
To clarify, the sample project in the module actually does build properly, but there's an issue with using the dependencies in any other app, where it pulls from jitpack. See this gradle as an example that won't build.
I've tried using combinations of api, implementation, #aar, and transitive.
Come to think of it, this may be a jitpack issue and not a gradle issue, but if anyone else has a resolution I'd like to hear it.
I no longer have access to any of the libraries dependencies. I can only use code and references from my library itself.
It is correct.
From the gradle docs :
dependencies {
api 'commons-httpclient:commons-httpclient:3.1'
implementation 'org.apache.commons:commons-lang3:3.5'
}
Dependencies appearing in the api configurations will be
transitively exposed to consumers of the library, and as such will
appear on the compile classpath of consumers.
Dependencies found in the implementation configuration will, on the
other hand, not be exposed to consumers, and therefore not leak into
the consumers' compile classpath. This comes with several benefits:
dependencies do not leak into the compile classpath of consumers anymore, so you will never accidentally depend on a transitive
dependency
faster compilation thanks to reduced classpath size
less recompilations when implementation dependencies change: consumers would not need to be recompiled
cleaner publishing: when used in conjunction with the new maven-publish plugin, Java libraries produce POM files that
distinguish exactly between what is required to compile against the
library and what is required to use the library at runtime (in other
words, don't mix what is needed to compile the library itself and what
is needed to compile against the library).
The issue seems to be related to the android-maven-gradle-plugin
Issue Report
It's has been fixed in version "2.0" of android-maven-gradle-plugin
just update to
dependencies {
classpath 'com.github.dcendents:android-maven-gradle-plugin:2.0'
}
or using the new syntax since Gradle 2.1
plugins {
id "com.github.dcendents.android-maven" version "2.0"
}
using api in your library module allows you to access the transient dependencies only in your library code; not the apps that consume it.
so to achieve the desired effect you need to change in your sample module.
implementation project(':core')
to
api project(':core')
note you don't need to use api in your library it's better to use implementation as it speeds up your build.

Categories

Resources