Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Why is Res class internal? #4327

Closed
guillermolc opened this issue Feb 17, 2024 · 13 comments
Closed

Why is Res class internal? #4327

guillermolc opened this issue Feb 17, 2024 · 13 comments
Assignees
Labels
question Not a bug, but question or comment resources

Comments

@guillermolc
Copy link

Hi guys, im trying to use the new feature of resources but i want to access to the resources of some common lib on another
but... the generated class is declerated as internal and i cant have access to that resources

can you check this use case?
image

@guillermolc guillermolc added enhancement New feature or request submitted labels Feb 17, 2024
@dima-avdeev-jb
Copy link
Contributor

Thanks for an Issue!

@pjBooms
Copy link
Collaborator

pjBooms commented Feb 19, 2024

Hi! It is by design. To access resources from another module you need to export accessors from the library where resources are introduced.

@guillermolc
Copy link
Author

Interesting @pjBooms but what is the correct way to do that?

@dima-avdeev-jb dima-avdeev-jb added question Not a bug, but question or comment and removed enhancement New feature or request labels Feb 19, 2024
@m0rtis
Copy link

m0rtis commented Feb 20, 2024

@pjBooms you mean something like this?

@OptIn(ExperimentalResourceApi::class)
object PublicRes {
    object drawable {
        val my_awesome_icon = Res.drawable.my_awesome_icon
    }
}

But what are the benefits of using Compose Resources library if I have to manually describe each resource? My usecase - reusable library of compose elements (single design system for all applications).

@xiaozhikang0916
Copy link
Contributor

Maybe an option to control the visibility of the generated Res class?

Extracting all resources in one common lib for others is a good practice, IMO.

@jfyoteau
Copy link

jfyoteau commented Feb 23, 2024

As a temporary solution, you can add in the build.gradle.kts of "resource" module, the following code:

// After executing the "generateComposeResClass" task, we replace the internal stuff by public.
tasks.named("generateComposeResClass") {
    doLast {
        val dirName = buildString {
            val group = project.group.toString()
                .lowercase()
                .replace('.', '/')
                .replace('-', '_')
            append(group)
            if (group.isNotEmpty()) {
                append("/")
            }
            append(project.name.lowercase())
        }
        val dir = project.layout.buildDirectory
            .dir("generated/compose/resourceGenerator/kotlin/$dirName/generated/resources")
            .get()
            .asFile
        File(dir, "Res.kt").also {
            if (!it.exists()) {
                return@also
            }
            val content = it.readText()
            val updatedContent = content.replace("internal object Res {", "object Res {")
            it.writeText(updatedContent)
        }
        listOf("Drawable0.kt", "String0.kt").forEach { filename ->
            File(dir, filename).also { file ->
                if (!file.exists()) {
                    return@also
                }
                val content = file.readText()
                val updatedContent = content.replace("internal val Res", "val Res")
                file.writeText(updatedContent)
            }
        }
    }
}

After that, you can access to "Res" class of "resource" module in the others compose modules.

@terrakok
Copy link
Collaborator

@OptIn(ExperimentalResourceApi::class)
object PublicRes {
    object drawable {
        val my_awesome_icon = Res.drawable.my_awesome_icon
    }
}

no. it doesn't work because multimodule projects are not supported. The recommended way to do that is

class ModuleRes {
  fun getIconPainter() = painterResource(Res.module.icon)
}

but againt it won't work until we support multimodule projects

@terrakok
Copy link
Collaborator

terrakok commented Feb 26, 2024

I see a lot of confusion about the internal-ness of the Res class.

⚠️ First of all, it is required at the moment because the resources don't work in multimodule projects. ⚠️

But I'll try to explain an initial idea behind that:

  • The Res class is a container of static accessors for module's files. It reflects a file hierarchy by fact and it will be changed on each change in files.
  • It is a bad idea to make a public API of a module (another words an API of library) depending on the autogenerated code.
  • If you want to make a library provide some resources as its API then declare it explicitly. like
@Composable
fun getMyLibraryIconPainter(): Painter = painterResource(ID)

It guaranties a stability of the API.

Yes, it is not convenient for design-system or icon-pack libraries. I would generate the proposed code by the kotlin poet in my project for that :)

Since there is a huge request to add the feature to the gradle plugin. I added the feature to my backlog and will investigate pros and cons and there is a chance to have it in the future releases.

@terrakok terrakok changed the title Generated res class Why is Res class internal? Feb 26, 2024
@JavierSegoviaCordoba
Copy link
Contributor

The main issue is almost all companies use something like Lokalise, and that means having only one module for resources and the rest of the modules depend on it.

The use case of generating a public API would be interesting for a lot of people.

@terrakok
Copy link
Collaborator

terrakok commented Feb 26, 2024

To clarify why it is not a trivial task to change the class modifier only: because resources are packed different way on each target. So, to support the correct work with multimodule projects we have to be sure that the runtime may select correct path to read.

@m0rtis
Copy link

m0rtis commented Mar 16, 2024

It is a bad idea to make a public API of a module (another words an API of library) depending on the autogenerated code.

@terrakok I'm sorry, but with all my great respect for you, I can't agree with that.
Suppose I have a library in which a gradle plugin generates some code. I really use such solutions in my projects. For example, I wrote an api client for YouTrack, in which files are generated during assembly based on the openapi description of the YouTrack API. These generated files are baked into the build artifact and shipped with the library. Of course, the YouTrack API may change in the future. In this case, I will simply update the version of my library and publish this version with newly generated files corresponding to the updated API.

Another example is compose web, which generates js and wasm files in one module of my application (composeApp). And I configured gradle so that the files generated by the compose plugin are copied to the build-folder of another module (server). And the ktor server serves these generated files as static resources.

Something similar would be cool to implement with compose resources - during the build of the library with resources, the resource files are baked into the final artifact (jar) and accessible from the application using the library. And if I change something in the icon library, I'll just publish a new version of this library. And an application that uses the previous version will only see the previous version of the icons. And if you update the version, new versions of the icons will become available.

@terrakok
Copy link
Collaborator

#4482

@hakanai
Copy link

hakanai commented Apr 8, 2024

The main issue is almost all companies use something like Lokalise, and that means having only one module for resources and the rest of the modules depend on it.

Yikes, I didn't realise that Lokalise had this limitation of only one module having resources. That sort of thing definitely goes into the list of things to consider when choosing a translation vendor.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Not a bug, but question or comment resources
Projects
None yet
Development

No branches or pull requests

9 participants