A Gradle plugin that acts as your project's gardener, carefully pruning unused resources from your Android codebase to keep it clean and maintainable.
-
Accurate Detection: Detects resource usage through multiple patterns:
- Direct R class references (
R.drawable.icon,R.string.app_name) - Import aliases (
import ...R as LibR→LibR.drawable.icon) - XML resource references (
@string/app_name,@drawable/icon) - Theme attribute references (
?attr/colorPrimary) - Custom View attribute references via R.styleable (
R.styleable.CustomView_customAttr) - Implicit style parent references via dot notation (
TextStyle.Bodyinherits fromTextStyle) - ViewBinding usage (
ActivityMainBinding,FragmentHomeBinding) - Paraphrase library support (
FormattedResources.greeting_format()) - AndroidManifest.xml references (
@style/Theme.App)
- Direct R class references (
-
Smart Generated Code Handling: This plugin is aware of code generation and correctly handles:
- ViewBinding: Only detects layouts that are actually used via binding classes
- Paraphrase: Only detects ICU-formatted strings where
FormattedResources.xxx()is actually called
-
Safe Operation:
- Preview mode to see changes before pruning
- Configurable exclusion patterns for resources that should never be removed
- Per-variant tasks for fine-grained control
-
Multi-Module Support: Library resources used by dependent app modules are correctly detected as used, preventing false positive pruning
-
Build Variant Aware: Detects resources across all source sets for each variant:
- Base resources (
src/main/res) - Build type specific resources (
src/debug/res,src/release/res) - Product flavor specific resources (
src/staging/res, etc.)
- Base resources (
- Android Gradle Plugin 8.0+
- Gradle 8.0+
- Java 17+
Add the following to your project:
pluginManagement {
repositories {
gradlePluginPortal()
mavenCentral()
google()
}
}plugins {
id("com.android.application") // or com.android.library
id("net.syarihu.resource-pruner") version "<latest-version>"
}pluginManagement {
repositories {
mavenLocal() // For local development
gradlePluginPortal()
mavenCentral()
google()
}
}plugins {
id("com.android.application") // or com.android.library
id("net.syarihu.resource-pruner") version "<latest-version>-SNAPSHOT"
}Preview which resources would be removed without actually deleting them:
./gradlew pruneResourcesPreviewDebug
# or for release variant
./gradlew pruneResourcesPreviewReleaseExample output:
=== Analysis Results ===
Total resources: 24
Resources to preserve: 12
Resources to prune: 12
Unused resources:
drawable: 1
- ic_unused_icon
layout: 1
- layout_unused
string: 4
- unused_string
- another_unused
- deprecated_message
- unused_paraphrase
To see which directories are being scanned, use --info:
./gradlew pruneResourcesPreviewDebug --infoRemove unused resources from your project:
./gradlew pruneResourcesDebug
# or for release variant
./gradlew pruneResourcesReleaseWarning: This operation modifies your source files. Make sure to commit your changes before running, or use
pruneResourcesPreviewfirst to preview.
Configure the plugin in your module's build.gradle.kts:
resourcePruner {
// Exclude resources matching these regex patterns from pruning
excludeResourceNamePatterns.addAll(
"^ic_launcher.*", // Preserve launcher icons
"^google_play_.*", // Preserve Google Play assets
"^app_name$", // Preserve app name
)
// Optional: Only target specific resource types
targetResourceTypes.addAll("drawable", "string", "layout")
// Optional: Exclude specific resource types from pruning
excludeResourceTypes.addAll("menu") // Preserve all menu resources
// Optional: Specify which source sets to scan
sourceSets.addAll("main", "debug")
}| Option | Type | Description |
|---|---|---|
excludeResourceNamePatterns |
List<String> |
Regex patterns for resource names to exclude from pruning |
targetResourceTypes |
Set<String> |
Resource types to target (empty = all types) |
excludeResourceTypes |
Set<String> |
Resource types to exclude from pruning (applied after targetResourceTypes) |
sourceSets |
Set<String> |
Source sets to scan for usage (default: ["main"]) |
scanDependentProjects |
Boolean |
Scan dependent projects for resource usage (default: true) |
cascadePrune |
Boolean |
Repeatedly prune until no unused resources remain (default: true, max 5 iterations) |
drawable,mipmap- Image resourceslayout- Layout XML filesmenu- Menu XML filesraw- Raw resources (audio, video, JSON, text, etc.)string,plurals- String resourcescolor- Color resourcesdimen- Dimension resourcesstyle- Style resourcesanimator,anim- Animation resourcesbool,integer,array- Value resourcesattr- Attribute resources
For large multi-module projects, you can centralize the plugin configuration using Gradle's convention plugins pattern.
Note for Gradle 8.x users: This plugin is compiled with Kotlin 2.2.x, while Gradle 8.x uses Kotlin 2.0.x internally. If you encounter a
Module was compiled with an incompatible version of Kotlinerror, add the following to yourbuild-logic/build.gradle.kts:tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach { compilerOptions { freeCompilerArgs.add("-Xskip-metadata-version-check") } }This is not needed when using Gradle 9.x or when applying the plugin directly via
plugins { }block without convention plugins.
1. Add the plugin to your version catalog (gradle/libs.versions.toml):
[versions]
resource-pruner = "<latest-version>"
[libraries]
resource-pruner-gradle-plugin = { module = "net.syarihu.resourcepruner:resource-pruner-gradle-plugin", version.ref = "resource-pruner" }2. Register the plugin in your root settings.gradle.kts:
pluginManagement {
repositories {
gradlePluginPortal()
mavenCentral()
google()
}
plugins {
id("net.syarihu.resource-pruner") version "<latest-version>"
}
}3. Configure build-logic/settings.gradle.kts:
pluginManagement {
repositories {
gradlePluginPortal()
mavenCentral()
google()
}
plugins {
id("net.syarihu.resource-pruner") version "<latest-version>"
}
}
dependencyResolutionManagement {
versionCatalogs {
create("libs") {
from(files("../gradle/libs.versions.toml"))
}
}
}4. Add the plugin dependency to build-logic/build.gradle.kts:
plugins {
`kotlin-dsl`
}
// For Gradle 8.x only - see note above
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
compilerOptions {
freeCompilerArgs.add("-Xskip-metadata-version-check")
}
}
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
dependencies {
implementation(libs.android.gradlePlugin)
implementation(libs.resource.pruner.gradle.plugin)
}5. Create a configuration function (ResourcePrunerConfiguration.kt):
import net.syarihu.resourcepruner.gradle.ResourcePrunerExtension
import org.gradle.api.Project
import org.gradle.kotlin.dsl.configure
internal fun Project.configureResourcePruner() {
extensions.configure<ResourcePrunerExtension> {
excludeResourceNamePatterns.addAll(
"^ic_launcher.*", // Preserve launcher icons
"^google_play_.*", // Preserve Google Play assets
)
}
}6. Apply in your convention plugins (precompiled script plugin example):
// convention.android.app.gradle.kts
plugins {
id("com.android.application")
id("net.syarihu.resource-pruner")
}
configureResourcePruner()// convention.android.library.gradle.kts
plugins {
id("com.android.library")
id("net.syarihu.resource-pruner")
}
configureResourcePruner()Or if you prefer class-based convention plugins:
// AndroidApplicationConventionPlugin.kt
class AndroidApplicationConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
pluginManager.apply("com.android.application")
pluginManager.apply("net.syarihu.resource-pruner")
configureResourcePruner()
}
}
}This ensures consistent resource pruner configuration across all modules in your project.
- Resource Collection: Scans
res/directories to find all defined resources - Usage Detection: Analyzes source code and XML files for resource references
- Smart Filtering:
- Skips ViewBinding generated code (detects actual binding class usage instead)
- Skips Paraphrase generated code (detects actual
FormattedResources.xxx()calls instead)
- Exclusion Matching: Applies configured exclusion patterns
- Analysis/Pruning: Reports or removes unused resources
The plugin correctly handles ViewBinding-only layouts:
// This layout is detected as used even without R.layout.xxx reference
class ItemViewHolder(
private val binding: ItemViewbindingOnlyBinding
) : RecyclerView.ViewHolder(binding.root)The plugin correctly handles Paraphrase ICU-formatted strings:
<!-- strings.xml -->
<string name="greeting_format">Hello, {name}!</string>
<string name="unused_format">Unused: {value}</string>// Only greeting_format is detected as used
val greeting = getString(FormattedResources.greeting_format(name = "World"))
// unused_format will be marked for pruning since FormattedResources.unused_format() is never calledThe plugin automatically detects resource usage across module boundaries. When analyzing a library module, it scans the source code of all projects that depend on it.
Important: You only need to apply the plugin to modules whose resources you want to manage. Dependent modules (app modules that use the library) do NOT need the plugin - they will be scanned automatically.
project-root/
├── app/ # Uses resources from common-ui
│ └── build.gradle.kts # implementation(project(":common-ui"))
│ # ← No plugin needed here
└── common-ui/ # Library with shared resources
└── build.gradle.kts # Has resource-pruner plugin
When you run ./gradlew :common-ui:pruneResourcesPreviewDebug, the plugin will:
- Find all projects that depend on
common-ui(in this case,app) - Scan those projects' source code for resource references
- Correctly identify library resources that are used by dependent modules
This prevents false positives where library resources appear unused because they're only referenced in app modules.
For libraries used by multiple modules, the plugin scans all of them to ensure resources are only pruned when truly unused:
project-root/
├── app-a/ # Uses resource_a from common-ui
├── app-b/ # Uses resource_b from common-ui
└── common-ui/ # Has resource_a, resource_b, resource_c
# → Only resource_c will be pruned
If your app module also has its own resources to manage, you can apply the plugin there too:
// app/build.gradle.kts
plugins {
id("com.android.application")
id("net.syarihu.resource-pruner") // Optional: only if app has its own resources to prune
}To disable dependent project scanning (e.g., for standalone library development):
resourcePruner {
scanDependentProjects.set(false)
}See the example/ and example-lib/ directories for a complete example demonstrating:
- ViewBinding detection (including ViewBinding-only layouts)
- Paraphrase integration
- Multi-module resource usage detection
- Various unused resources that get pruned
This project was inspired by gradle-unused-resources-remover-plugin.
Copyright 2026 syarihu
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
