Home > database >  Kotlin running code in multiple Scopes works, but not when ran in the same one
Kotlin running code in multiple Scopes works, but not when ran in the same one

Time:12-06

I am trying to poplulate values from a datastore. I only want to recover them from the datastore once, which is why I am canceling the job after 1 second. This prevents it constantly updating.

This does not work. (1)

suspend fun setupDataStore(context: Context) {
    tempDataStore = TempDataStore(context)
    val job = Job()
    val scope = CoroutineScope(job   Dispatchers.IO)
    scope.launch {
        tempDataStore.getDieOne.collect {
            die1.value = it!!.toInt()
        }
        tempDataStore.getDisplayText.collect {
            displayText.value = it!!
        }
        tempDataStore.getDieTwo.collect {
            die2.value = it!!.toInt()
        }
    }
    delay(1000L)
    job.cancel()
}

This does not work. (2)

suspend fun setupDataStore(context: Context) {
    tempDataStore = TempDataStore(context)
    val job = Job()
    val scope = CoroutineScope(job   Dispatchers.IO)
    scope.launch {
        tempDataStore.getDieOne.collect {
            die1.value = it!!.toInt()
        }
    }
    scope.launch {
        tempDataStore.getDisplayText.collect {
            displayText.value = it!!
        }
    }
    scope.launch {
        tempDataStore.getDieTwo.collect {
            die2.value = it!!.toInt()
        }
    }
    delay(1000L)
    job.cancel()
}

This does work! (3)

suspend fun setupDataStore(context: Context) {
     tempDataStore = TempDataStore(context)
     val job = Job()
     val job2 = Job()
     val job3 = Job()
     val scope = CoroutineScope(job   Dispatchers.IO)
     val scope2 = CoroutineScope(job2   Dispatchers.IO)
     val scope3 = CoroutineScope(job3   Dispatchers.IO)
     scope.launch {
          tempDataStore.getDieOne.collect {
             die1.value = it!!.toInt()
         }
     }
     scope2.launch {
          tempDataStore.getDisplayText.collect {
             displayText.value = it!!
         }
     }
     scope3.launch {
          tempDataStore.getDieTwo.collect {
              die2.value = it!!.toInt()
         }
     }
     delay(1000L)
     job.cancel()
     job2.cancel()
     job3.cancel()
}

Here is the TempDataStore class (4)

class TempDataStore(private val context: Context) {

    companion object{
        private val Context.dataStore by preferencesDataStore(name = "TempDataStore")
        val DISPLAY_TEXT_KEY = stringPreferencesKey("display_text")
        val DIE_ONE = stringPreferencesKey("die_one")
        val DIE_TWO = stringPreferencesKey("die_two")
    }

    val getDisplayText: Flow<String?> = context.dataStore.data
        .map { preferences ->
            preferences[DISPLAY_TEXT_KEY] ?: "Roll to start!"
        }

    suspend fun saveDisplayText(text: String) {
        context.dataStore.edit { preferences ->
            preferences[DISPLAY_TEXT_KEY] = text
        }
    }

    val getDieOne: Flow<String?> = context.dataStore.data
        .map { preferences ->
            preferences[DIE_ONE] ?: "1"
        }

    suspend fun saveDieOne(dieOne: Int) {
        context.dataStore.edit { preferences ->
            preferences[DIE_ONE] = dieOne.toString()
        }
    }

    val getDieTwo: Flow<String?> = context.dataStore.data
        .map { preferences ->
            preferences[DIE_TWO] ?: "2"
        }

    suspend fun saveDieTwo(dieTwo: Int) {
        context.dataStore.edit { preferences ->
            preferences[DIE_TWO] = dieTwo.toString()
        }
    }

    suspend fun resetDataStore() {
        context.dataStore.edit { preferences ->
            preferences.clear()
        }
    }
}

It is being called from a composable screen.

LaunchedEffect(true) {
   sharedViewModel.setRoles()
   sharedViewModel.saveChanges()
   sharedViewModel.setupDataStore(context)
}

I was expecting (1) to work. It should run all of them at the same time and return the results accordingly. Instead of populating all of the values, it only populates the first one called. (3), works but I want to understand why it works and not (1) and (2).

CodePudding user response:

Calling collect on a Flow suspends the coroutine until the Flow completes, but a Flow from DataStore never completes because it monitors for changes forever. So your first call to collect prevents the other code in your coroutine from ever being reached.

I'm not exactly why your second and third attempts aren't working, but they are extremely hacky anyway, delaying and cancelling as a means to avoid collecting forever.


Before continuing, I think you should remove the nullability from your Flow types:

val getDieOne: Flow<String?>

should be

val getDieOne: Flow<String>

since you are mapping to a non-nullable value anyway.


I don't know exactly what you're attempting, but I guess it is some initial setup in which you don't need to repeatedly update from the changing values in the Flows, so you only need the first value of each flow. You can use the first value to get that. Since these are pulling from the same data store, there's not really any reason to try to do it in parallel. So your function is pretty simple:

suspend fun setupDataStore(context: Context) {
    with(TempDataStore(context)) {
        die1.value = getDieOne.first().toInt()
        displayText.value = getDisplayText.first()
        die2.value = getDieTwo.first().toInt()
    }
}

CodePudding user response:

If you want just the first value, why not using the .first() method of Flow? And then you shouldn't need those new scopes!

Try out something like this:

suspend fun setupDataStore(context: Context) {
    tempDataStore = TempDataStore(context)
    die1.value = tempDataStore.getDieOne.first().toInt()
    displayText.value = tempDataStore.getDisplayText.first()
    die2.value = tempDataStore.getDieTwo.first().toInt()
}

EDIT: Thanks Tenfour04 for the comment! You're right. I've fixed my code.

  • Related