i'm introducing compose desktop into a java/swing ...
# compose-desktop
b
i'm introducing compose desktop into a java/swing app and having trouble figure out how to load resources (an icon). In swing, this works:
MyClass::class.java.getResource("/images/icon.png")
, but if I try to use it in compose:
painterResource("/images/icon.png")
it throws an exception with
Resource not found
. I've tried with
images/icon.png
and
./images/icon.png
. Does the path need to be different? The files are located in
src/main/resources
. I looked at the compose resource library but a lot of the code still uses the java resources as-is so i'm hesitant to convert over, and the project is currently
kotlin("jvm")
, not
kotlin("multiplatform")
oddly, the IDEA compose preview window shows the icon fine
r
That's because the preview window looks at the file system vs getResource that can resolve paths inside a jar
m
b
I already read through that
the functions under "Using Java resources" is what i'm using
m
The compose way of doing it is to add the dependency:
Copy code
implementation(compose.components.resources)
Then put an image into the right folder like this, e.g.:
Copy code
composeApp/src/commonMain/composeResources/drawable/picture_as_pdf_24px.xml
and then you can call it in your code like this:
Copy code
Icon(painterResource(Res.drawable.picture_as_pdf_24px), null)
b
that's if its 100% compose, but it's a mix, so i need the Swing components to be able to load the resources the java way
that document says that it's possible to still use them as java resources in compose, I just can't seem to get that to work
a
I have something like this:
Copy code
fun loadIconBytes(resourcePath: String): ByteArray {
    val stream = javaClass.getResourceAsStream(resourcePath) ?:
        throw MissingResourceException("Image for icon of $this not found", javaClass.name, resourcePath)
    return stream.use { it.readBytes() }
}

private val painterCache = mutableMapOf<String, Painter?>()


@OptIn(ExperimentalResourceApi::class)
@Composable
fun cachedResourcePainter(resourcePath: String): Painter? {
    return painterCache.getOrPut(resourcePath) {
        runCatching {
            val bytes = loadIconBytes(resourcePath)
            BitmapPainter(bytes.decodeToImageBitmap())
        }.getOrNull()
    }
}

@Composable
private fun ResourceImage(
    resourcePath: String,,
    modifier: Modifier = Modifier,
    contentDescription: String?,
    alignment: Alignment = Alignment.Center,
    contentScale: ContentScale = ContentScale.Fit,
    alpha: Float = DefaultAlpha,
    colorFilter: ColorFilter? = null
) {
    val painter = cachedResourcePainter(resourcePath)
    if (painter != null) {
        Image(
            painter = painter,
            contentDescription = contentDescription,
            modifier = modifier,
            alignment = alignment,
            contentScale = contentScale,
            alpha = alpha,
            colorFilter = colorFilter,
        )
    } else {
        Image(
            imageVector = MissingResourceImageVector,
            contentDescription = contentDescription,
            modifier = modifier,
            alignment = alignment,
            contentScale = contentScale,
            alpha = alpha,
            colorFilter = ColorFilter.tint(LocalContentColor.current.copy(alpha = LocalContentAlpha.current))
        )
    }
}
I’m not sure you can use the same resources both from Swing and as Compose resources, because the former needs to be in
src/main/resources
and the latter in
src/main/composeResources
. @Konstantin Tskhovrebov knows for sure.
b
I'll try reading as resource like that and see if it works. As for the path, the docs say you can specify the resource path (in newer versions), but it does expect a specific subdirectory layout, which java code doesn't normally follow
using the stream directly worked, it must be searching the wrong classpath i guess?