Development of modern applications for the Android platform is impossible without the use of third-party solutions. Writing each component from scratch is a waste of time when ready-made tools exist for working with the network, databases, interfaces and analytics. Integrating such solutions into a project is one of the basic tasks that every developer faces at the start.

The process of connecting external modules in the environment Android Studio has undergone significant changes in recent years. If earlier developers often used manual copying of jar files or complex Maven configurations, today the build system has become the de facto standard. It automates the process of downloading, caching, and versioning dependencies, making the project more stable and reproducible across different machines. Gradle. It automates the process of loading, caching, and versioning dependencies, making your project more stable and reproducible across different machines.

Understanding how the dependency manager works is critical to avoiding version conflicts and increasing the size of the final APK file. Incorrect configuration can lead to compilation errors that are difficult for a newbie to track down. In this article we will analyze in detail all the current ways to add libraries to your project, from standard repositories to local modules.

Dependency management architecture in Gradle

The basis of modern project assembly in Android Studio is the configuration file build.gradle. It is important to understand that there are usually two such files in a project: one at the Project level and one at the Module level. It is in the second file, which often has a name build.gradle (Module: app), that the main dependencies of your application are written.

The Gradle system works on the principle of a declarative description of requirements. You do not download files manually, but only indicate the coordinates of the required library: group, artifact name and version. The package manager itself accesses remote repositories, downloads the necessary files and places them in a local cache on your computer. This ensures that all developers on the team are using identical versions of the libraries.

Particular attention should be paid to dependency configurations such as implementation, api or testImplementation. The configuration was previously widely used, but was deprecated due to problems with encapsulation and build speed. Using compile, but it was deprecated due to problems with encapsulation and build speed. Usage implementation hides the module's internal dependencies from those who use it, which speeds up the recompilation process when changes occur.

โš ๏ธ Warning: Never hard-fix library versions unless necessary, unless you use the Version Catalog. Using dynamic versions (for example 1.+) can cause a developer update to a library to break your build without your knowledge.

๐Ÿ’ก

Use a plugin libs.versions.toml to centrally manage versions of all libraries in a project. This allows you to update versions in one place and avoid desynchronization of dependencies in different modules.

The structure of the dependency block in the module file is as follows:

dependencies {

implementation"org.jetbrains.kotlin:kotlin-stdlib:1.9.0"

implementation"androidx.core:core-ktx:1.12.0"

testImplementation"junit:junit:4.13.2"

}

Searching and adding libraries from repositories

The most common source of libraries is a repository Maven Central and Google Maven. The vast majority of popular solutions, such as libraries from Google (Material Components, Lifecycle), are located there. To connect, it is enough to know the exact coordinates of the artifact, which are usually published on the official pages of projects or on the website MVN Repository.

The adding process begins with opening the file build.gradle application module. In the section dependencies you add a new line with the keyword implementation. After entering the coordinates, be sure to press the button Sync Now, which appears at the top of the editor, or select from the menu File โ†’ Sync Project with Gradle Files. Without this synchronization, the changes will not take effect and the IDE will not see the new classes.

Sometimes a situation arises when the required library is located in a specific repository that is not connected by default. In this case, you need to check the file settings.gradle at the project level. There, in the block dependencyResolutionManagement all used repositories should be listed. If the required source is not available, you need to add it explicitly by specifying the URL.

  • ๐Ÿ” MVN Repository โ€”the main search engine for finding the coordinates of Java and Android libraries with version history.
  • ๐Ÿ“ฆ Google Maven โ€”the official source of all AndroidX ecosystem libraries and Google Play Services tools.
  • ๐ŸŒ JitPack โ€”a popular service for connecting libraries directly from GitHub, GitLab or Bitbucket.
๐Ÿ“Š Where do you most often get library coordinates?
Official documentation
MVN Repository website
GitHub README
I copy from colleagues

To connect repository JitPackwhich is often used for libraries that are not included in Maven Central, the following structure is added to the file settings.gradle :

dependencyResolutionManagement {

repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)

repositories {

google

mavenCentral

maven { url'https://jitpack.io' }

}

}

Working with local files and modules

Not all libraries are available in remote repositories. Sometimes it is necessary to connect your own module developed within the team, or use a third-party library in the form .aar or .jar file downloaded manually. In such cases, the connection mechanism differs from the standard one and requires working with the project file system.

To connect a local module that has already been imported into the project structure (for example, a folder :my-local-lib), the project syntax is used. You simply reference the module path relative to the project root. This is ideal for a modular architecture, when a large application is divided into functional parts (feature modules) developed by different teams.

If you have a binary file .aar, it must be placed in a folder libs inside the application module. By default, this folder may not be present, so you need to create it manually. After placing the file in build.gradle the module, you should add configuration to flatly include files from this directory. This is Gradle to look for dependencies not only in the repositories, but also in the specified folder.

โš ๏ธ Attention: When using local .aar files, make sure that they do not contain transitive dependencies that are already in your project, but in other versions. This may cause a class conflict ClassCastException during application execution.

Example of connecting a local library from a folder libs:

android {

//... other settings

}

dependencies {

implementation fileTree(dir:'libs', include: ['.aar','.jar'])

implementation project(':my-local-module')

}

What is the difference between .jar and .aar?

The .jar file contains only Java code (bytecode), resources (images, layouts, strings) are not included in it. The .aar (Android Archive) file is an advanced format that includes both compiled code and Android resources, manifest, and ProGuard files. For Android libraries with interfaces, always use .aar.

Version control and conflict resolution

One โ€‹โ€‹of the most common problems when building a project is dependency conflict. This happens when two different libraries you use depend on the same third library, but require different versions of it. For example, library A requires Retrofit 2.9.0, and library B requires Retrofit 2.8.1. Gradle by default selects one version (usually the newest one), which can lead to errors if the second library is incompatible with it.

There is a powerful dependency analysis tool to diagnose such situations. By running the task Android Studio There is a powerful dependency analysis tool. By running the task dependencies through the terminal or the Gradle tab, you will receive a complete tree of all connected libraries. This allows you to see which library is pulling the conflicting version and make a decision to force version alignment.

To resolve the conflict, you can use the forced version selection mechanism. In a block configurations.all or directly in the dependency, you can specify which version to use for a particular artifact, ignoring the requirements of transitive dependencies. It is also a good practice to use Platform dependencies via platform or enforcedPlatform, especially for libraries from Google and Firebase, where the versions must be strictly synchronized.

Configuration type Visibility Purpose
implementation Internal Standard dependency, hidden from module consumers
api Public The dependency is transitively transferred to those who connect this module
testImplementation Test Available only in unit test code
debugImplementation Debug Connected only in debug build (for example, LeakCanary)
๐Ÿ’ก

Using configuration api instead implementation increases the project rebuild time, since any changes in this library force all dependent modules to be rebuilt. Use api only if the library classes are used in the public interface of your module.

Optimizing application size and ProGuard

Each connected library increases the size of the final APK file. When using a large number of third-party solutions, the size of the application can increase significantly, which negatively affects installation conversion. To combat this, Android Studio has built-in optimization mechanisms, such as ProGuard or its modern successor R8.

These tools perform obfuscation and code minification. They remove unused classes and methods from linked libraries, leaving only what is actually called in your application. However, some libraries use reflection or native calls that a static code analyzer cannot track. In such cases, the application may crash on the user's device, even if everything worked in the emulator.

To prevent the removal of the necessary code, libraries often come with their own rule files proguard-rules.pro. When connecting a library via Gradle, these rules are usually pulled up automatically. But if you experience crashes after enabling minification (in buildTypes release), you may need to manually add exceptions to your application's rules file, specifying which classes cannot be deleted or renamed.

  • ๐Ÿ—‘๏ธ Shrinking โ€” The process of removing unused code and resources.
  • ๐Ÿ” Obfuscation โ€” Renaming classes and methods to short names (a, b, c) to protect against reverse engineering.
  • ๐Ÿ“‰ Optimization โ€”improving the bytecode to improve execution performance.

An example of adding rules for a specific library to a file proguard-rules.pro:

# Preserving Gson library classes from obfuscation

-keep class com.google.gson.** { *; }

Saving models data

-keep class com.myapp.data.model.** { *; }

โš ๏ธ Warning: ProGuard configuration files are syntax sensitive. One extra dot or space can cause rules to fail and critical code to be removed. Always test the release build on a real device before publishing.

โ˜‘๏ธ Testing before release

Done: 0 / 4

Common errors and debugging methods

Even experienced developers encounter errors when synchronizing Gradle. The most common of them is Failed to resolve. This error means that the build system was unable to find the specified artifact in the connected repositories. The reasons may be trivial: a typo in the name of a group or artifact, lack of Internet, or the library was actually deleted from the repository.

Another common problem is Duplicate class. It occurs when two different libraries containing the same classes with the same package get into the project. This often happens when using old support (Support Library) and new AndroidX at the same time, or when connecting different versions of Google Play Services. The solution is to exclude the conflicting module from one of the dependencies.

To debug build problems, it is useful to use command line switches. Running a build with the flag --stacktrace or --info outputs a detailed log that indicates the exact location of the failure. Android Studio also has a tab Build at the bottom of the screen where you can filter messages by severity level and quickly jump to the problematic configuration file.

If Gradle freezes while downloading dependencies, the problem may be in the cache. Clearing the cache through the menu File โ†’ Invalidate Caches / Restart often solves mysterious problems when the IDE โ€œdoes not seeโ€ already downloaded files or shows outdated information about the project. In extreme cases, you can manually delete the folder .gradle in the user's home directory to force a complete reload of all libraries.

How to speed up the build when the Internet is weak?

Customize the file gradle.propertiesby adding parameters org.gradle.daemon=true i org.gradle.parallel=true. You can also set up a proxy service to cache dependencies within the companyโ€™s local network, so as not to download them from the Internet multiple times.

Where are the downloaded libraries physically stored on the computer?

By default, Gradle saves all downloaded dependencies to the user cache folder. On Windows this is usually C:\Users\UserName\.gradle\caches, on macOS and Linux - ~/.gradle/caches. Within this directory, files are organized according to a group and version structure. It is not recommended to delete them manually; it is better to use the cache clear command in the IDE.

Is it possible to use libraries written in pure Java in a Kotlin project?

Yes, absolutely. Kotlin is fully compatible with Java. You can connect any Java libraries (jars) to a Kotlin project without additional settings. The Kotlin compiler automatically converts Java code into a Kotlin-readable format. However, for convenience, it is recommended to use Kotlin versions of popular libraries (for example kotlinx-coroutines instead of java-concurrent), as they provide a more idiomatic API.

What if the library requires a minimum Android SDK version higher than in my project?

In this case Gradle will throw a build error. You have two ways: either increase the value in the file to the level required by the library (which may cut off some older devices), or find an alternative library that supports older versions of Android. Sometimes library authors provide older versions of artifacts with support for legacy devices. minSdkVersion in the file build.gradle to the level required by the library (which may cut off some older devices), or find an alternative library that supports older versions of Android. Sometimes library authors provide older versions of artifacts with support for legacy devices.

How to update all libraries in a project to the latest versions?

Android Studio has a built-in inspection. Go to the menu Code โ†’ Inspect Code or pay attention to the highlighting of versions in the file build.gradle. If a new version is available, the version number will be highlighted in color and a tooltip will appear on hover prompting you to update. There are also plugins, such as Gradle Versions Pluginthat generate a report of available updates in the console.

Why does the application crash with NoClassDefFoundError after adding a library?

This error often indicates that the class is present at compile time, but is not present at runtime on the device. Possible reasons: the library was added to the configuration compileOnly (for compilation only), problems with multidimensionality (Multidex) on older devices when the method exceeds the 65k limit, or a version conflict in which the required class was removed by the ProGuard tool.