Having a coroutines launch
to execute three functions.
scope.launch {
getPrefsOne()
getPrefsTwo()
getPrefsThree()
}
If the function is doing something other than accessing DataStore, all three functions are completed as expected. But if the function is accessing the DataStore,
private fun getPrefValueFromDataStore(key: String): Flow<Any> {
val prefsKey = stringPreferencesKey(key)
var dataStore: DataStore<Preferences> = dataStore
val value = dataStore.data.map { preferences ->
preferences[prefsKey] ?: false
}
return value
}
then only the first function is called.
But if put three functions in their own launch
block, all three are executed.
fun theLauncher() {
scope.launch {
getPrefsOne()
}
scope.launch {
getPrefsTwo()
}
scope.launch {
getPrefsThree()
}
}
Why, and how to run the three functions in one launch
block?
The testing code:
import android.util.Log
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.emptyPreferences
import androidx.datastore.preferences.core.stringPreferencesKey
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.map
import java.io.IOException
class TestPrefs(val dataStore: DataStore<Preferences>) {
private var isPrefsOneEnabled = MutableStateFlow(false)
private var isPrefsTwoEnabled = MutableStateFlow(false)
private var isPrefsThreeEnabled = MutableStateFlow(false)
val job = SupervisorJob()
val scope = CoroutineScope(Dispatchers.IO + job)
private fun getPrefValueFromDataStore(key: String): Flow<Any> {
val prefsKey = stringPreferencesKey(key)
var dataStore: DataStore<Preferences> = dataStore
val value = dataStore.data.catch { exception ->
// dataStore.data throws an IOException when an error is encountered when reading data
println("+++ !!! exp $exception in getPrefValueFromDataStore($key)")
if (exception is IOException) {
Log.e("+++", "+++ !!! Error reading preferences.", exception)
emit(emptyPreferences())
} else {
throw exception
}
}
.map { preferences ->
preferences[prefsKey] ?: false
}
return value
.also{
println("+++ 111 --- exit getPrefValueFromDataStore($key), ret: $it")
}
}
fun theLauncher() {
System.out.println("+++ ### enter theLauncher() Thread: ${Thread.currentThread().id}")
scope.launch {
getPrefsOne()
getPrefsTwo()
getPrefsThree()
}
// scope.launch {
// getPrefsTwo()
// }
// scope.launch {
// getPrefsThree()
// }
System.out.println("+++ --- exit theLauncher() Thread: ${Thread.currentThread().id}")
}
suspend fun getPrefsOne() {
System.out.println("+++ +++ +++ getPrefsOne() Thread: ${Thread.currentThread().id}")
getPrefValueFromDataStore("TEST_PREFS_KEY_1")
.collect {
println("+++ getPrefsOne111111() getPrefValueFromDataStore().collect println got $it")
isPrefsOneEnabled.value = it as Boolean
}
//}
}
suspend fun getPrefsTwo() {
System.out.println("+++ +++ +++ getPrefsTwo() Thread: ${Thread.currentThread().id}")
getPrefValueFromDataStore("TEST_PREFS_KEY_2")
.collect {
println("+++ getPrefsTwo222222() getPrefValueFromDataStore().collect println got $it")
isPrefsTwoEnabled.value = it as Boolean
}
}
suspend fun getPrefsThree() {
System.out.println("+++ +++ +++ getPrefsThree Thread: ${Thread.currentThread().id}")
getPrefValueFromDataStore("TEST_PREFS_KEY_3")
.collect {
println("+++ getPrefsTwo3333333() getPrefValueFromDataStore().collect println got $it")
isPrefsThreeEnabled.value = it as Boolean
}
}
}
calling it and the log for two cases:
val dataStore: DataStore<Preferences> by preferencesDataStore(name = USER_PREFERENCES_NAME, scope = scope)
override fun onCreate(savedInstanceState: Bundle?) {
... ...
val testPrefs = TestPrefs(dataStore)
testPrefs.theLauncher()
}
1. run three functions in its own launch block, all three functions are called:
+++ ### enter theLauncher() Thread: 2
+++ +++ +++ getPrefsOne() Thread: 14581
+++ +++ +++ getPrefsTwo() Thread: 14583:
+++ --- exit theLauncher() Thread: 2
+++ 111 --- exit getPrefValueFromDataStore(TEST_PREFS_KEY_1), ret: TestPrefs$getPrefValueFromDataStore$$inlined$map$1@e6b5b99
+++ 111 --- exit getPrefValueFromDataStore(TEST_PREFS_KEY_2), ret: TestPrefs$getPrefValueFromDataStore$$inlined$map$1@af4ce5e
+++ +++ +++ getPrefsThree Thread: 14582
+++ 111 --- exit getPrefValueFromDataStore(TEST_PREFS_KEY_3), ret: TestPrefs$getPrefValueFromDataStore$$inlined$map$1@9e5470c
+++ getPrefsTwo3333333() getPrefValueFromDataStore().collect println got false
+++ getPrefsTwo222222() getPrefValueFromDataStore().collect println got false
+++ getPrefsOne111111() getPrefValueFromDataStore().collect println got false
2. run three functions in one launch block, only first function is called
+++ ### enter theLauncher() Thread: 2
+++ --- exit theLauncher() Thread: 2
+++ +++ +++ getPrefsOne() Thread: 14611
+++ 111 --- exit getPrefValueFromDataStore(TEST_PREFS_KEY_1), ret: TestPrefs$getPrefValueFromDataStore$$inlined$map$1@9e5470c
+++ getPrefsOne111111() getPrefValueFromDataStore().collect println got false
Update: @Joffery answered the question with details and the StateFlow's doc link.
Here is some other post may help if anyone runs into similar question:
A StateFlow never completes, so collecting it is an infinite action. This is explained in the documentation of StateFlow. Coroutines are sequential, so if you call collect on a StateFlow, none of the code after that call in the coroutine will ever be reached.
You're not getting single values here. You're collecting infinite flows. The point of a flow is not to get one asynchronous value but rather get updates. In the case of DataStore
, I believe it returns an infinite flow which gives you all further updates to the preference.
This means that .collect { ... }
on such a flow will suspend indefinitely.
When you run a single launch
, you're running a single coroutine that executes each function sequentially. The first needs to complete before the other ones are executed. But the first one is stuck on an infinite collection, so it appears as if it hangs.
If your goal is indeed to have 3 concurrent (and infinite) collections of events which maintain those isPrefsXEnabled
boolean flows, then they have to be in separate coroutines, because that's how you would express this concurrency.