Alexander Maryanovsky
05/03/2026, 8:48 PMprivate sealed interface PainterResult {
data object Loading: PainterResult
class Success(val painter: Painter) : PainterResult
data object Failure: PainterResult
}
@Composable
private fun cachedEveResourcePainter(eveObject: EveObjectWithIcon): State<PainterResult> {
val cached = EveObjectPainterCache[eveObject]
if (cached != null) return cached
val state = mutableStateOf<PainterResult>(PainterResult.Loading)
EveObjectPainterCache[eveObject] = state
val mainDispatcher = rememberCoroutineScope().coroutineContext[CoroutineDispatcher]!!
GlobalScope.launch {
println("Loading icon for $eveObject")
val bytes = withContext(IO_DISPATCHER) {
runCatching {
eveObject.loadIconBytes()
}.getOrNull()
}
val painter = withContext(Dispatchers.Default) {
runCatching {
if (bytes == null) null else BitmapPainter(bytes.decodeToImageBitmap())
}.getOrNull()
}
state.value = if (painter == null) PainterResult.Failure else PainterResult.Success(painter)
println("Finished loading icon for $eveObject: $state")
withContext(mainDispatcher) {
println("Value for $eveObject in main thread: ${state.value}")
}
}
LaunchedEffect(Unit) {
snapshotFlow { state.value }.collect { result ->
println("State value for $eveObject: $result")
}
}
return state
}
both prints in the main thread say the value is “Loading” and the calling function never draws the icon.
If I wrap the assignment to state.value with Snapshot.withMutableSnapshot then the prints say the right thing, but still the calling function never draws the icon because it doesn’t get auto-recomposed.Carter
05/03/2026, 9:03 PMAlexander Maryanovsky
05/03/2026, 9:07 PMCarter
05/03/2026, 9:18 PMWriting a MutableState.value in a background thread never becomes visible in the main thread
Alexander Maryanovsky
05/03/2026, 9:22 PMAlexander Maryanovsky
05/03/2026, 9:24 PMwithContext(mainDispatcher) {
state.value = ...
}
but I’m not after a solution.Carter
05/03/2026, 9:25 PMAlbert Chang
05/04/2026, 3:19 AMAlexander Maryanovsky
05/04/2026, 8:59 AMfun main() = singleWindowApplication {
Text(loadValue().value)
}
var cachedState: State<String>? = null
@OptIn(DelicateCoroutinesApi::class)
private fun loadValue() : State<String> {
cachedState?.let { return it }
val state = mutableStateOf("Loading")
cachedState = state
GlobalScope.launch {
Snapshot.withMutableSnapshot {
state.value = "Loaded"
}
}
return state
}Alexander Maryanovsky
05/04/2026, 8:59 AM