Skip to content

CMP-4196 Add support for subdirectories in resource folders#5591

Open
cmelchior wants to merge 2 commits intoJetBrains:masterfrom
cmelchior:cm/resource-subdir-support
Open

CMP-4196 Add support for subdirectories in resource folders#5591
cmelchior wants to merge 2 commits intoJetBrains:masterfrom
cmelchior:cm/resource-subdir-support

Conversation

@cmelchior
Copy link
Copy Markdown
Contributor

@cmelchior cmelchior commented May 4, 2026

Fixes https://youtrack.jetbrains.com/issue/CMP-4196/Add-ability-to-have-subfolders-in-composeResources

This commit adds support for subdirectories in /drawable-* and /font-*/ folders. The feature can be controlled using ResourceHolder.allowAccessorSubDirs.

The following logic has been added:

  • Subdirectories are transformed to object classes under the appropriate Res.<type>, e.g., Res.drawable.subdir. This puts some limitations on the naming, e.g., no symbols, no starting digit, no whitespace. Conversion rules can be added later, similar to how some file names are transformed.
  • Qualified resources will match across subdirectories, similar to how it is done now on the top-level.
  • It is disallowed to have directories and files create the same key, .e.g., /drawable/subdir/ and /drawable/subdir.png will throw an exception.

The logic for validating resource files has been improved:

  • Calculating available ResourceItem's have been isolated in a new class: ResourceHolder, making it easier to unit test the logic and keep Gradle Tasks cleaner.
  • All invalid input errors are now GradleException, which provides a better experience in the Gradle output.
  • Illegal characters in file names, like whitespace or . will now show a proper exception message.
  • Files that would accidentally merge resource keys now throw an exception, e.g., /drawable/compose.png and /drawable/compose.jpg.
  • We now ignore case when collapsing resource directories. Previously, /drawable/COMPOSE.png and /drawable/compose.png would be two different keys, but this logic would break on case-insensitive filesystems. Now they collapse to the same key. The keyfrom the unqualified directory (e.g., /drawable) is used.
  • String keys across files could be duplicated without error, making it undefined which one was returned. This will now throw an exception

Testing:

  • All tests in ResourceTest pass.
  • Added new validation logic tests in ResourceHolderValidationTest.
  • https://github.com/JetBrains/compose-multiplatform/tree/master/components/resources example has been modified to show it working.
  • Manual testing shows that Android Studio preview still works
  • Check for the platform (Win/Linux/Mac) before running some of the validation cases. Some of them will be prevented outright by the host filesystem.
  • Fix Release Notes

Open Questions:

  • In this PR, the subdirectory support is enabled by default. However, the Compose Multiplatform Plugin is not yet aware of these changes. Because it optimistically re-creates the accessors when a user adds new resources or renames them, the plugin will break subdir support in these cases. This PR can be merged with ResourceHolder.allowAccessorSubDirs = false, and then the flag can be toggled once the Plugin has been updated. I'm not sure what the best approach is here?

  • Some of the validation logic is now stricter. It is technically a breaking change to collapse resource names while ignoring their casing, but the previous implementation had bugs that didn't show up until the app was run on the "wrong" platform. If it is not acceptable to break this in this PR, a migration strategy should probably be put in place instead. But guidance here is needed.

Release Notes

Features - Multiple Platforms

  • Support for subfolders in /composeResources/drawables and /composeResources/fonts

This commit adds support for subdirectories in `/drawable-*` and `/font-*/` folders. This feature can be controlled using `ResourceHolder.allowAccessorSubDirs`.

- Subdirectories are transformed to object classes under the appropriate `Res.<type>`, e.g. `Res.drawable.subdir`. This puts some limitations on the naming, .e.g no symbols, no starting digit, no whitespace.
Conversion rules can be added later, similar to how some file names are transformed.
- Qualified resources will match across subdirectories, similar to how it is done now on the top-level.
- It is disallowed to have directories and files create the same key, .e.g. `/drawable/subdir/` and `/drawable/subdir.png` will throw an exception.

Also the logic for validating resource files have been improved:

- Calculating available ResourceItem's have been isolated in a new class: `ResourceHolder`, making it easier to unit test the logic and keep Gradle Tasks cleaner.
- All invalid input errors are now GradleException which provides a better experience in the Gradle output.
- Illegal characters in file names, like whitespace or `.` will now show a proper exception message.
- Files that would accidentially merge resource keys now throw an exception, e.g. `/drawable/compose.png` and `/drawable/compose.jpg`
- We now ignore case when collapsing resource directories. Previously `/drawable/COMPOSE.png` and `/drawable/compose.png` would be two different keys, but this logic would break on case-insensitive filesystems. Now they collapse to the same key. The one from the un-qualified directory (e.g `/drawable`) is used.
- String keys across files could conflict without error. This will now throw an exception
@MatkovIvan MatkovIvan requested a review from terrakok May 4, 2026 11:35
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.

1 participant