Caring for your dependency garden: An approach to Android Dependency Management

Part 1: Preparing your garden bed — Consistency is best & general tips

Katie Barnett
Bilue Product Design & Technology Blog
5 min readJul 18, 2022

--

A woman waters flowers in a flowerbed
Keeping everything nice and tidy makes for happy developers (image by E.F.S. on shutterstock.com)

With bigger and better Android applications there is always some cool feature to add. As all experienced engineers know, it is much better to rely on a (respected) library or SDK rather than continually reinventing the wheel. We of course want to make sure the dependencies that we use are up to date and don’t include any security vulnerabilities or bugs and we don’t want to add these dependencies one day, then at a later time stop using them and leave them in the codebase to cause bloat or confusion to future developers. Just like in a garden we don’t want old, dead and diseased plants cluttering up our garden bed and impacting our pretty flowers and useful veggies.

If we take advantage of useful techniques of dependency inclusion to manage versions (especially when writing good CLEAN code and separating out concerns into modules or moving dependencies into separate gradle files) we lose access (or just visibility) to that useful little yellow line in Android Studio that used to tell us that the dependency has a newer version out there.

So what do we do? Does this mean we have to take time every day or every release to search for each dependency in Maven and check the version we are using is the latest? Should we read the documentation for each version and make sure we know that all code is safe? Should we check that we are using all our included dependencies with every release? And do we have the right configuration for all of these?

This seems like a lot of replanting and weeding to do in our dependency garden, especially as many of the great libraries we use are constantly being updated. So how can we automate this and make our lives easier?

Let’s look at some excellent gradle plugins, CI/CD features and tips and tricks to make dependency management as easy and predictable as possible!

Something to note before we start planning our garden, for Android build scripts, we can use the Groovy DSL or Kotlin script (KTS) / Kotlin DSL. For the purposes of these examples I have used Groovy as it is more widely known at the moment, but all the plugins and tips mentioned can be done in Kotlin DSL as well if desired. For more information on this check out the migration to KTS docs here.

There is a lot to unpack and implement on our path to getting first place at the garden show, I have broken up the various areas into 4 parts and will share a post each week on Mondays.

Part 1: Preparing your garden bed: Consistency is best & general tips

Part 2: Replanting the annuals: Is it time to update that dependency?

Part 3: Get out your pruning shears: Detecting unused & incorrectly configured dependencies in Android

Part 4: Pulling out the weeds: Suspicious and vulnerable dependencies

And without further ado, Part 1…

Part 1: Preparing your garden bed: Consistency is best & general tips

An untidy flowerbed full of overgrown grass and yellow flowers sits in front of a fence
Don’t let that flower bed get out of control (image by E.F.S. on shutterstock.com)

Some general guidance on managing dependencies and making it easy for future gardeners to find what they need. I have included links to the official documentation for each suggestion so you can find out more detailed steps as needed:

Repositories

  • Use the settings.gradle file to manage your common repositories in one place
  • Make sure you are only including the remote repositories you need and have them in the right order — from my own experience this can speed up your syncs and builds considerably if you have the most general repositories declared first.
  • Remove JCenter as it is now only read only repository and won’t be updated in the future (so no point searching it for any new dependencies).
    If this is not possible, add a comment indicating which SDK it is used for so that when that SDK is updated elsewhere the JCenter reference can be removed

Version Management

  • Define the versions and other project wide properties in an external block in your root build.gradle file, if you have a lot of dependencies you can create these in a external file and include this in your root file as
    apply from: 'constants.gradle'
    This means you can refer to the same version for each dependency in all modules and avoid conflicts and only make an update in one place.
    For example, in constants.gradle:

Then in your build.gradle for the module:

  • Always use an explicit value for your version — don’t use the dynamic version format like 2.+ or snapshot versions (unless you need to temporarily for testing something) as these will force gradle to frequently check for new versions and will impact your build times. Another impact is changes to that dependency could be introduced that you may not be aware of, best case causing a failed build that will require you to update the code, worse case will introduce code with security implications.

Readability

  • Order your dependencies in the dependency block or list as logically as possible.
    For example, modules at the top, then regular dependencies grouped logically, followed by test dependencies. Also add in comments to indicate why you are including a group of dependencies to make it easy for future developers to understand.
  • Better yet, create lists of dependencies in an additional gradle file so you can refer to them logically, using our constants example from above, we would have a new gradle file, dependencies.gradle (include it in the same way):

And then in the build.gradle for the module we can use:

Doing this gives you structure to your dependencies and makes it easy to see what each group is being used for.

Clarity

  • Don’t include all .jar files or directories by default, you may accidentally include an old version or a file that has just been added to the file system and never removed. If you need to include a library from the files, include it explicitly rather than the whole directory.
    For example, don’t use this:
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    Instead:
    implementation files('libs/specific_library_jar.jar')

Performance & configuration

  • Use implementation rather than api if you can — keep the dependencies to the module that they are used in unless they are used across many modules (or put the code that uses them in a core module) — see Part 3 for more on detecting this.

Start how you mean to continue

  • When starting a project, put into place all the plugins and practices to keep your dependencies up to date and configured correctly, it is much better to do this incrementally while actively developing rather than finding out just prior to release that you have problematic or unused dependencies — when a lot of these tech debt tasks get done in my experience.
  • Put as much of this routine work as you can into your CI/CD system, set up triggers to alert you and your development team via chat apps or email.

Check out Part 2 to find out how to easily tell when a dependency is ready for an update and how to include this as part of your normal build process.

--

--