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

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

Katie Barnett
Bilue Product Design & Technology Blog
5 min readAug 1, 2022

--

A woman waters some plants while a young girl tends to a small potplant.
Removing any dead or unused plants will make the rest of the garden grow better! (image by E.F.S. on shutterstock.com)

Joining here and missed Parts 1 and 2? Check out why we need to manage our dependency garden, some useful tips and tricks and plugins to keep them updated.

While having extra dependencies for an Android app is not a big issue and the build system will not include any code into the package that are not used, it does add to potential confusion and bloat for any future developers. Someone else working on the project in the future may see several different SDKs included that do the same thing and not use the one that is used elsewhere in the app. Having extra dependencies that are not used also just makes the build scripts harder to read and more unmaintainable, scaring off any efforts to rationalise each inclusion. Just the same as if we had many large dead plants cluttering up our garden bed, the sunlight and nutrients will not get to our good flowers!

Only including what you use also gives gains in reducing the sync and build time of the app, there are some great statistics on this here.

A bigger issue is incorrectly configured plugins, if you are using api when you could be using implementation you may be exposing that dependency to downstream modules and rebuilding those other modules every time the api dependency changes. You may also be able to get app size efficiencies by using compileOnly configurations.

To manually address these issues you could search for the packages of each dependency to see if they are used or do a guess and check approach by removing or changing the configuration of each and see if the app builds. This obviously would take a huge amount of time and would be rather error prone!

So let’s make use of the very useful Dependency Analysis Plugin (full documentation here). This plugin will provide a report on the dependency usage for the app or SDK, it will provide the advice as follows (from their documentation):

Dependency-related advice

Unused dependencies which should be removed.

Declared dependencies which are on the wrong configuration (api vs implementation vs compileOnly). This is variant-aware, so it might tell you to use debugImplementation, for example.

Transitively used dependencies which ought to be declared directly, and on which configuration.

Dependencies which could be declared on the compileOnly configuration, as they’re not required at runtime.

Annotation processors which could be removed as unused.

Plugin-related advice

Plugins that are applied but which can be removed. (currently we support kapt, java-library, and kotlin-jvm for redundancy-checking)

To add the plugin, it is as simple as just adding a plugin block to your root build.gradle (with dependency_analysis_plugin_version being the current plugin version, found here1.10.0 at time of writing):

You can also configure how the plugin will behave for each issue type — this helps when running this from your CI/CD set up (more on this later). By default the plugin will just give a warning, but as we know, warnings often get ignored!

My recommended project root level configuration for the plugin are as follows. The below configuration will run for all modules (see documentation for how to exclude or configure specific modules).

The report can be run as a gradle task as using ./gradlew buildHealth and a report with suggestions will be printed in the console.

Note: the minimum required version of Gradle is 7.0. For other limitations see here.

Exclusions / Special Cases

Where needed we can add in exclusion rules or special cases if a particular library’s usage is not being picked up by the tool. This can be excluded on a library and/or module basis and can be done when there is no alternative.

It is best to use the exclusion rules rather than disabling the tool entirely and be as specific as possible.

An example of this:

For information on this kind of filtering & exclusion rules see here.

Example Output

Alternatives

The Nebula (Gradle Lint) Plugin exists but due to some open issues this is not able to be used on Android projects. This could be revisited if the plugin gets updated to support Android projects as this plugin is more well known & has more features (including gradle tasks that will automatically fix issues).

If you are aware of an alternative that provides better or more features that can be automated into the CI or development process please share in the comments!

CI Integration

This can be set up on most CIs that allow you to run a script or specific gradle tasks.

My approach here is to fail the CI build if there are any recommendations from the plugin except for Transitive Dependencies which will be reported at a warning level (see configuration listed above). The thinking behind this is that the use of the parent dependency may still be in development (and the usage will be expanded in the future) or that the parent dependency is more well known and documented for use (i.e. searching for the release notes of the sub dependency may not be as easy as the parent dependency). It is recommended that these warnings be reviewed by the development team and adjusted as needed depending on the use case.

The CI script below will create both the machine readable json reports and a more human friendly report to build/reportes/dependency-analysis/. These can then be exposed as artefacts in the CI.

The CI task to complete this analysis should be done immediately after building (prior to running tests or other analysis) so that the build result is shared back to the team as soon as possible so can be fixed without causing the build to spend time running tests and delaying the failure report.

CI job script

An example script that can be used to to pass the failure to a CI such as Bitrise is as follows:

$PIPESTATUS variable is used as tee does not output the task result.

Check out Part 4 to do some weeding — for when dependency inclusion goes wrong —detecting and removing suspicious and vulnerable dependencies.

--

--