I'm trying to fetch some resources from disk, but ...
# coroutines
a
I'm trying to fetch some resources from disk, but it's not clear to me how to add all of them to a Map without an arbitrarily long blocking delay to wait for coroutines to finish.
Copy code
@OptIn(ExperimentalResourceApi::class, DelicateCoroutinesApi::class)
    private fun fetchStringResources(paths: Map<String, String>) : Map<String, String> {
        val results = ConcurrentHashMap<String, String>()
        runBlocking {
            for ((name, path) in paths) {
                GlobalScope.launch {
                    val string = resource(path).readBytes().decodeToString()
                    results.put(name, string)
                }
            }
            delay(1_000) // without this line, `results` is empty
        }
        return results.toMap()
    }
Part of why this comes up is because I was trying to make all calling functions
suspend
functions, but Composable functions can't use that modifier. I haven't really figured out the proper structure for an application yet, but maybe I should be passing the strings map as arguments from a viewmodel (or similar)? Could that viewmodel be suspendable, so I don't have to use
launch
, and can use a regular mutableMap with
.await()
before passing to composable functions?
Okay, to partially answer my question, this is better:
Copy code
@OptIn(ExperimentalResourceApi::class)
    suspend private fun fetchStringResources(paths: Map<String, String>) : Map<String, String> {
        val results = mutableMapOf<String, String>()
            for ((name, path) in paths) {
                val string = resource(path).readBytes().decodeToString()
                results.put(name, string)
        }
        return results.toMap()
    }

    private val allTranslations by lazy {
        runBlocking { fetchTranslations() }
    }
But
runBlocking
should be avoided when possible.
e
I would avoid `ConcurrentHashMap`; you can launch all the IO in parallel while collecting the results sequentially
Copy code
val entries = channelFlow {
    for ((name, path) in paths) {
        launch {
            val string = resource(path).readBytes().decodeToString()
            send(name to string)
        }
    }
}
val results = buildMap {
    entries.collect { (name, string) ->
        put(name, string)
    }
}
👍 1
composable functions should usually use
LaunchedEffect
to get data from
suspend
functions, as per https://developer.android.com/jetpack/compose/side-effects#launchedeffect
but sometimes
collectAsState[WithLifecycle]
or
produceState
work better, if it's not just one-shot
a
I managed to access the coroutine's resulting value in a composable using `produceState`:
Copy code
val alertText: String by produceState (alertTextId) {
        value = lookupString(alertTextId)
    }
Where
lookupString
is now a suspend function 🎉
Thanks for your help! : )
e
that behaves similar to
Copy code
var alertText by remember { mutableStateOf(alertTextId) }
LaunchedEffect { alertText = lookupString(alertTextId) }
e.g.
alertText = alertTextId
until the suspend function returns. I'd be surprised if that's what you want…
a
For now, I decided not to go with LaunchedEffect, and calling lookupString as a suspend function. Each fetched variable required about three lines of code each, which seemed like too much for fetching more than one string in a function.