Is this a bug, or am I missing something? Writing ...
# compose
a
Is this a bug, or am I missing something? Writing a MutableState.value in a background thread never becomes visible in the main thread:
Copy code
private 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.
c
Why not use a MutableStateFlow, which is designed for safe publication across threads?
🚫 1
a
I’m not trying to communicate across threads. I’m just trying to load and cache icons. But that’s beside the point. I’m not looking for an alternative solution. I want to understand what’s wrong with this one, and if there’s a bug in Compose Runtime.
c
How are you not trying to communicate across threads?
Writing a MutableState.value in a background thread never becomes visible in the main thread
a
Imagine you’re trying to use an API to load an icon. And the API returns a `StateFlow<Icon>`… I would immediately delete that API and throw my computer out the window 🙂
1
But again, that’s beside the point. I’m not looking for a solution. A simple solution would be to just
Copy code
withContext(mainDispatcher) {
    state.value = ...
}
but I’m not after a solution.
c
I can specifically speak with confidence to the Java memory model in general, which is not eventually consistent. Without some kind of safe publication, then never seeing an update is possible. withContext(mainDispatcher) would ensure safe publication. MutableStateFlow also ensures safe publication because it is designed to.
a
Might be this.
a
Yes, that looks like exactly the same case. A simple reproducer:
Copy code
fun 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
}
Thanks