Skip to content

Conversation

0spol
Copy link

@0spol 0spol commented Sep 30, 2025

Hi everyone! 👋 This is my first PR to this project. I’d like to propose a change to centralize dependency management, and I’d really appreciate your feedback on whether this approach makes sense. Please let me know if any adjustments are needed.

I tested it locally using:

./gradlew testGplayDebugUnitTest --stacktrace

I had some minor issues with the tests during the development. I checked Dependabot, and everything seems compatible without problems.

Context:

Currently, this project declares dependencies directly in each build.gradle(.kts) file, which leads to:

  • Repeated version numbers across modules
  • Harder upgrades when updating libraries
  • Less clarity about which dependencies are official AndroidX/Compose vs. third-party

Proposal:

Introduce a libs.versions.toml (Gradle Version Catalog) to centralize all dependency and plugin versions. This would:

  • Keep all versions in one place ([versions])
  • Allow using human-readable aliases for libraries and plugins ([libraries] and [plugins])
  • Improve maintainability and readability, especially as more modules are migrated to Compose
  • Separate official AndroidX/Compose dependencies from third-party libraries for clarity

Proposed Steps:

  1. Create gradle/libs.versions.toml and define all current dependencies and plugin versions.
  2. Replace implementation, testImplementation, etc., in all build.gradle(.kts) files with the corresponding libs.* aliases.

Benefits:

  • Easier upgrades and consistent dependency versions across modules
  • Clear separation of official Android libraries vs. third-party
  • Future-proof for Compose migration and multi-module setups

Thanks in advance for your help and feedback! 🙇‍♂️

@@ -0,0 +1,70 @@
[versions]
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we have all versions

espresso = "3.7.0"

[libraries]
# AndroidX
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we have all the libraries linked with his versions

uiautomator = { module = "androidx.test.uiautomator:uiautomator", version.ref = "uiautomator" }
espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "espresso" }

[plugins]
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we have all the plugins linked with his versions

@@ -1,8 +1,8 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here I'm changing id for alias.
libs.plugins.android.application
refers to
android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" }
in libs.versions.toml

@@ -1,8 +1,8 @@
import com.android.build.gradle.internal.tasks.factory.dependsOn

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here I'm changing all the references to use libs.versions.toml

@0spol
Copy link
Author

0spol commented Oct 2, 2025

Okay, then.
That's my proposal to use version catalogs. If you have any questions, I'm here 🫡.

Comment on lines 14 to 19
compileSdk = libs.versions.compileSdk.get().toInt()

defaultConfig {
applicationId = "me.hackerchick.catima"
minSdk = 21
targetSdk = 36
minSdk = libs.versions.minSdk.get().toInt()
targetSdk = libs.versions.targetSdk.get().toInt()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Putting these versions in the Gradle Version Catalog doesn't seem to be a common practice from what I could tell from other apps.

Are you sure this is a good idea?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was a long conversation with one of my friends. From what I’ve seen, some Android open projects are starting to use this approach.


Build Logic with Compose

There’s also the build-logic approach used with Compose to normalize versions and library settings:

This setup is quite complicated and really only makes sense in projects with huge modularization. (God bless this guys)


In practice, I think three approaches are used depending on project size and modularization level:

  • Inline versions → simplest approach, fine for projects with no modularization.
  • Inline versions + libs.versions.toml → good for projects that may use modularization in the future, centralizes dependency versions.
  • Build-logic → best for large, modular projects, though less common because of its complexity.

So it depends on your desing idea 😅

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do think it's probably better if we keep the mindSdk/targetSdk/compileSdk numbers in build.gradle.kts directly, without the libs.versions code. I'm not seeing anything about that in the Android docs and neither in your links, nor in other apps. And Catima is a fairly simple app. Have flexibility for future modularization sounds nice, but let's also try to keep it as simple where possible :)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, let's try to make it simple. To be honest it's a new approach to put minSdk/targetSdk/compileSdk in libs.versions.toml that I saw here Compose Samples, they have started using this but we could keep it simple, doesn't make a big difference 😊

Comment on lines 114 to 150
dependencies {
// AndroidX
implementation("androidx.appcompat:appcompat:1.7.1")
implementation("androidx.constraintlayout:constraintlayout:2.2.1")
implementation("androidx.core:core-ktx:1.17.0")
implementation("androidx.core:core-remoteviews:1.1.0")
implementation("androidx.core:core-splashscreen:1.0.1")
implementation("androidx.exifinterface:exifinterface:1.4.1")
implementation("androidx.palette:palette:1.0.0")
implementation("androidx.preference:preference:1.2.1")
implementation("com.google.android.material:material:1.13.0")
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.5")
implementation(libs.appcompat)
implementation(libs.constraintlayout)
implementation(libs.core.ktx)
implementation(libs.core.remoteviews)
implementation(libs.core.splashscreen)
implementation(libs.exifinterface)
implementation(libs.palette)
implementation(libs.preference)
implementation(libs.material)
coreLibraryDesugaring(libs.desugar)

// Third-party
implementation("com.journeyapps:zxing-android-embedded:4.3.0@aar")
implementation("com.github.yalantis:ucrop:2.2.10")
implementation("com.google.zxing:core:3.5.3")
implementation("org.apache.commons:commons-csv:1.9.0")
implementation("com.jaredrummler:colorpicker:1.1.0")
implementation("net.lingala.zip4j:zip4j:2.11.5")
implementation(libs.zxing.embedded)
implementation(libs.ucrop)
implementation(libs.zxing.core)
implementation(libs.commons.csv)
implementation(libs.colorpicker)
implementation(libs.zip4j)

// Crash reporting
val acraVersion = "5.13.1"
implementation("ch.acra:acra-mail:$acraVersion")
implementation("ch.acra:acra-dialog:$acraVersion")
implementation(libs.acra.mail)
implementation(libs.acra.dialog)

// Testing
val androidXTestVersion = "1.7.0"
val junitVersion = "4.13.2"
testImplementation("androidx.test:core:$androidXTestVersion")
testImplementation("junit:junit:$junitVersion")
testImplementation("org.robolectric:robolectric:4.16")

androidTestImplementation("androidx.test:core:$androidXTestVersion")
androidTestImplementation("junit:junit:$junitVersion")
androidTestImplementation("androidx.test.ext:junit:1.3.0")
androidTestImplementation("androidx.test:runner:$androidXTestVersion")
androidTestImplementation("androidx.test.uiautomator:uiautomator:2.3.0")
androidTestImplementation("androidx.test.espresso:espresso-core:3.7.0")
testImplementation(libs.test.core)
testImplementation(libs.junit)
testImplementation(libs.robolectric)

androidTestImplementation(libs.test.core)
androidTestImplementation(libs.junit)
androidTestImplementation(libs.test.ext.junit)
androidTestImplementation(libs.test.runner)
androidTestImplementation(libs.uiautomator)
androidTestImplementation(libs.espresso.core)
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I personally wish we could avoid using aliases completely, it really complicates reading the config. But well, Google has decided this is the new standard so we should move along with it.

But I'd like to at least ask: can we make the aliases be as close to the actual library names as possible? That way there is less searching that needs to be done to know what library we're talking about.

So like:
androidx.test:core -> androidx-test-core (I think that's the best we can do)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using this approach, a dependency like:

androidTestImplementation("androidx.test.ext:junit:1.3.0") // build.gradle.kts

would transform into a centralized declaration inside [libraries] block in libs.versions.toml:

androidx-test-ext-junit = { module = "androidx.test.ext:junit", version.ref = "androidXTest" }

And in the [versions] block:

androidXTest = "1.3.0"

And finally

androidTestImplementation(libs.androidx.test.ext.junit) // So we can't do libs-androidx-test-ext-junit 🫠 

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That does sound like a huge improvement with regards to readability to me, to be honest. I know I'm "the odd one out" with this though :)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the beginning, I know it can be a bit confusing, but later it’s really helpful to have each version in a single file. When I was learning this, it was a little confusing, and when I discovered modulation and custom builds, it actually got even more complicated hahaha.

That said, just let me know. I know Android recommends this approach, but if you think this way doesn’t make sense for this project, we can just skip it :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I may have explained myself unclearly, sorry for confusing you, I'm trying to understand this stuff better myself too.

I mostly mean that if we use aliases like libs.test.core it becomes more confusing to know what library it is and what "stack" it's part of. So I'd rather have the alias names be close to the real library name wherever possible, like libs.androidx.test.core or whatever (I don't really know where you're supposed to use . and where - in this Gradle Version Catalog stuff yet, so I may be naming the wrong type here).

I'm fine with switching to the Gradle Version Catalog. I don't think it is really better for Catima necessarily, but it is the new Google standard and if there's anything I've learned from 5+ years of Android dev it's that if you don't keep moving along with the standard at some point it becomes really painful :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sorry, but I don't understand how that explanation is relevant to my request to use alias names closer to the actual library names for easier recognition of which library it is? 😅

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might promise you that I'm not the CEO of Android promoting the use of Gradle Version Catalog, I'm not trying to talk you into using it.

If you think this approach doesn't make any sense and it's confusing, let's give up. The way I understood the use of libs.versions.toml was by looking modularized projects, they declare versions and libraries in one file and use them in each module, for me it isn't about easier recognition, it's more about structure and usability.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I... just want you to use clearer names than libs.test.core but okay?...

Copy link
Author

@0spol 0spol Oct 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh, I can't. Gradle doesn't take the reference without libs.plugins* or libs.* . I was thinking you didn't know about the concept. (I'm a native Spanish speaker, sorry if I missed something).

I could add androidx if it's more clearer, it's not a big deal 😅

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand the libs. prefix is needed yeah.

In my example I also said: libs.androidx.test.core instead of libs.test.core (because that looks more like androidx.test:core, which is the original name)

So yes, in that case, adding androidx would fix it.

Another example:
libs.zip4j -> libs.net.lingala.zip4j.zip4j

That way I don't have to look as deeply into other files to know what actual libraries are used, I can still see that in a single glance (mostly) :)

@0spol
Copy link
Author

0spol commented Oct 10, 2025

Well, I made some changes being more explicit with the libs names. However, if something is confusing let me know, I omitted some repetitions like appcompat:appcompat to be less redundant, also com and org have been omitted.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants