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

[Compose Resources] MissingResourceException in Auto-Updater application with different folders #4887

Closed
OleksiiHromovych opened this issue May 28, 2024 · 1 comment · Fixed by #4895
Assignees
Labels
bug Something isn't working resources

Comments

@OleksiiHromovych
Copy link

OleksiiHromovych commented May 28, 2024

I am working with a multi-module project. To create a desktop application, I use a launcher with automatic updates (update4j), and the application itself.

As a result, 2 folders with jar files are formed:
The main with launcher & auto-updates - C:\Program Files\MyApp\app
With an application that is downloaded from the launcher - C:\Users\Admin\AppData\Local\MyApp\lib

There is a problem with access to resources when starting the application itself due to the use of painterResource.


Exception in thread "Thread-1" org.jetbrains.compose.resources.MissingResourceException: Missing resource with path: composeResources/myapp.updater.generated.resources/drawable/compose-multiplatform.xml
	at classpath//org.jetbrains.compose.resources.ResourceReader_desktopKt$getPlatformResourceReader$1.getResourceAsStream(ResourceReader.desktop.kt:29)
	at classpath//org.jetbrains.compose.resources.ResourceReader_desktopKt$getPlatformResourceReader$1.read(ResourceReader.desktop.kt:7)
	at classpath//org.jetbrains.compose.resources.ImageResourcesKt$loadImage$2.invokeSuspend(ImageResources.kt:150)
	at classpath//org.jetbrains.compose.resources.ImageResourcesKt$loadImage$2.invoke(ImageResources.kt)
	at classpath//org.jetbrains.compose.resources.ImageResourcesKt$loadImage$2.invoke(ImageResources.kt)
	at classpath//org.jetbrains.compose.resources.AsyncCache$getOrLoad$2$deferred$1$1.invokeSuspend(AsyncCache.kt:19)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at classpath//kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:104)
	at classpath//kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:277)
	at classpath//kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:95)
	at classpath//kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:69)
	at classpath//kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
	at classpath//kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:48)
	at classpath//kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
	at classpath//org.jetbrains.compose.resources.ResourceState_blockingKt.rememberResourceState(ResourceState.blocking.kt:46)
	at classpath//org.jetbrains.compose.resources.ImageResourcesKt.vectorResource(ImageResources.kt:83)
	at classpath//org.jetbrains.compose.resources.ImageResourcesKt.painterResource(ImageResources.kt:40)
	at classpath//ApplicationKt$launchApplication$1.invoke(Application.kt:19)
	at classpath//ApplicationKt$launchApplication$1.invoke(Application.kt:14)
	at classpath//androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jb.kt:116)
	at classpath//androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jb.kt:33)
	at classpath//androidx.compose.ui.window.Application_desktopKt$application$1$1.invoke(Application.desktop.kt:117)
	at classpath//androidx.compose.ui.window.Application_desktopKt$application$1$1.invoke(Application.desktop.kt:116)
	at classpath//androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jb.kt:116)
	at classpath//androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jb.kt:33)
	at classpath//androidx.compose.ui.window.Application_desktopKt$awaitApplication$2$1$2$1$1.invoke(Application.desktop.kt:233)
	at classpath//androidx.compose.ui.window.Application_desktopKt$awaitApplication$2$1$2$1$1.invoke(Application.desktop.kt:232)
	at classpath//androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jb.kt:107)
	at classpath//androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jb.kt:33)
	at classpath//androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228)
	at classpath//androidx.compose.ui.window.Application_desktopKt$awaitApplication$2$1$2$1.invoke(Application.desktop.kt:223)
	at classpath//androidx.compose.ui.window.Application_desktopKt$awaitApplication$2$1$2$1.invoke(Application.desktop.kt:221)
	at classpath//androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jb.kt:107)
	at classpath//androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jb.kt:33)
	at classpath//androidx.compose.runtime.ActualJvm_jvmKt.invokeComposable(ActualJvm.jvm.kt:33)
	at classpath//androidx.compose.runtime.ComposerImpl.doCompose(Composer.kt:3303)
	at classpath//androidx.compose.runtime.ComposerImpl.composeContent$runtime(Composer.kt:3236)
	at classpath//androidx.compose.runtime.CompositionImpl.composeContent(Composition.kt:725)
	at classpath//androidx.compose.runtime.Recomposer.composeInitial$runtime(Recomposer.kt:1071)
	at classpath//androidx.compose.runtime.CompositionImpl.composeInitial(Composition.kt:633)
	at classpath//androidx.compose.runtime.CompositionImpl.setContent(Composition.kt:619)
	at classpath//androidx.compose.ui.window.Application_desktopKt$awaitApplication$2$1$2.invokeSuspend(Application.desktop.kt:221)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at classpath//kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:104)
	at java.desktop/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:318)
	at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:773)
	at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:720)
	at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:714)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
	at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:86)
	at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:742)
	at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
	at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
	at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
	at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90)

Maybe the problem is that the jar file used to search for resources is in another directory (the launcher is in the main one).

The search is performed using the BuiltinClassLoader.findResourceOnClassPath method, line return ucp.findResource(name, false);

  • When trying to launch via compose desktop/run, the URL is returned file:/D:/Projects/IntelliJ/MyApp/composeApp/build/libs/composeApp-jvm.jar!/composeResources/MyApp.composeApp.generated.resources/drawable/compose-multiplatform.xml
  • When starting via the launcher, composeApp-jvm.jar is not in the list of loaders.

Formation and installation through distributionForCurrent works. All files are in one directory.

Maybe I can additionally specify the path to the files of the main application, or is there another solution to the problem?

Affected platforms

  • desktop

Versions

  • Compose Multiplatform version: 1.7.0-beta01
  • Compose plugin: 1.6.10
  • Kotlin version: 2.0.0
@OleksiiHromovych
Copy link
Author

Found a "dirty fix" for this situation:

application {
	 Thread.currentThread().contextClassLoader = this.javaClass.classLoader
         Window(...

Explanation
In PlatformResourceReader, ClassLoader is obtained as follows return Thread.currentThread().contextClassLoader ?: this.javaClass.classLoader!!
But contextClassLoader refers to the launcher loader.

It's interesting that

The general appearance of the launch function after the launcher has the following appearance, for convenience I write which ClassLoader is used by launcher/app

  override fun run(context: LaunchContext) { // app
         thread { // app
             application { // launcher
            
		 Window(...
             }
         }
     }

That is, for some reason, the application call will change the ClassLoader to another one, although the thread itself will remain the one we defined. Overriding thread(contextClassLoader= ...) does not help. But if you assign another loader in the application block, then everything is fine.

@terrakok terrakok added bug Something isn't working and removed enhancement New feature or request labels May 29, 2024
terrakok added a commit that referenced this issue May 29, 2024
…argets (#4895)

The class loader retrieval method has been modified in both
`ResourceReader.android.kt` and `ResourceReader.desktop.kt` files. The
return statement has been changed to prioritize java class classLoader
and provides a clearer error message when it can't be found.

Fixes #4887
Fixes #4742

## Release Notes
### Fixes - Resources
- Delete contextClassLoader usage on JVM targets
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working resources
Projects
None yet
3 participants