https://kotlinlang.org logo
Title
o

Olivier Patry

03/19/2021, 2:03 PM
I try using
vectorXmlResource
but it fails with a NPE, form
openResourceStream
. I guess my problem comes from my Gradle configuration not packaging my assets in the final Jar file but I don't know how to fix that. Any clue?
desktop-app/src/main/kotlin
for the source code,
desktop-app/src/main/resources
for the assets. I put an
icons/
dir within
resources/
which contains my Android Vector drawables and then I use
vectorXmlResource("icons/ic_settings.xml")
.
desktop-app/build.gradle.kts
import org.jetbrains.compose.compose
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
    kotlin("jvm")
    id("org.jetbrains.compose")
    application
}

group = "org.acme"
version = "1.0.0"

dependencies {
    implementation(compose.desktop.currentOs)
}

tasks.withType<KotlinCompile> {
    kotlinOptions.jvmTarget = "1.8"
}

application {
    mainClass.set("MainKt")
}
(Android Studio properly sees the
resources/
dir as an assets directory)
i

Igor Demin

03/19/2021, 4:39 PM
Can you attach a project with a minimal reproducer? I checked, xml resources work in this template. Configuration is the same as you described.
o

Olivier Patry

03/19/2021, 5:22 PM
I started a project from scratch close to my setup and it works, I'll dig from here to understand the diff
It's madness… with the toy project, everything works like a charm. I managed to make it working in my app but it's totally RANDOM… It seems
Modifier.size(48.dp)
make the crash easier but even without that, I have random crashes. If Iaunch it several times in sequence…
In fact, it's as random with or without
Modifier.size(48.dp)
i

Igor Demin

03/19/2021, 6:13 PM
We use
Thread.currentThread().contextClassLoader
Maybe something somewhere overrides it? Try to call this before `vectorXmlResource`:
Thread.currentThread().contextClassLoader = ClassLoader.getSystemClassLoader()
(just to be sure that this is because of
contextClassLoader
)
o

Olivier Patry

03/19/2021, 6:30 PM
Yes, you found it!
It's now 100% reliable
I'm surprised, Compose runtime is multithreaded?
Do you consider this as a bug on Compose side or should I manage this on my side?
i

Igor Demin

03/19/2021, 6:44 PM
I'm surprised, Compose runtime is multithreaded?
It is single threaded for now, but there are plans to make it multi-threaded.
this as a bug on Compose side or should I manage this on my side
I think we can replace
Thread.currentThread().contextClassLoader
by
ClassLoader.getSystemClassLoader()
in Compose (we just need to be sure that everything will work as before). You also can manage this on your side, if somewhere in your code
contextClassLoader
is changed. Usually we need to override
contextClassLoader
only for some part of the code and then revert it:
val contextClassLoader = Thread.currentThread().contextClassLoader
    try {
        Thread.currentThread().contextClassLoader = ...
        ...
    } finally {
        Thread.currentThread().contextClassLoader = contextClassLoader
    }
Things get complicated if some third-party library changes it and forgets to revert.
👍 1
o

Olivier Patry

03/19/2021, 7:07 PM
So, something I don't understand is: If Compose is monothreaded, why did I have random failure? I don't tweak any classloaders.
What I do though is launching asynchronous work using
Executors.newSingleThreadExecutor()
but all of this is wrapped using
suspendCoroutine {}
In other circumstances, such background tasks invokes callback on their thread which I delegate to a view model class which itself updates a
MutableStateFlow
with
MainScope
coroutine scope
private val viewModelScope: CoroutineScope = MainScope()

    private val _collections = MutableStateFlow<LibraryState>(LibraryState.Unloaded)
    val collections: Flow<LibraryState>
        get() = _collections

.... something called from background ....
            viewModelScope.launch(context = Dispatchers.Main) {
                _collections.value = LibraryState.Updated(collectionModels)
            }
....
Then, on compose side
val state by libraryViewModel.collections.collectAsState(LibraryState.Unloaded)
So maybe this setup leads to odd behavior?
I can't apply you suggestion with the try/finally:
Try catch is not supported around composable function invocations.
I'll do it another way
I might have another lead for the root cause of the issue. The code I mentioned which triggers the data update triggering
collectAsState
recomposition comes from a native library. I know, on Android, there are some oddities with class loaders and JNI for threads created from native side. Maybe it's something linked to this too on desktop?
i

Igor Demin

03/20/2021, 10:13 AM
Maybe it's something linked to this too on desktop?
Yes, threads created from native side have no
contextClassLoader
. Do you control the code which is called from native side? You can call
Thread.currentThread().contextClassLoader = ClassLoader.getSystemClassLoader()
there, not in Composable function.
o

Olivier Patry

03/20/2021, 10:21 PM
Given that I have a custom
icon
helper, I'll do the trick here for now. There are to much code being called from native callbacks right now. Thanks 👍