Search code examples
androidkotlinsharedpreferenceskotlin-coroutines

Confusion about what DataStore methods are supported


TLDR: I've been fighting most of the day today with DataStore, more specifically why most suspend methods seem to just hang forever and only map {}.first worked for me to get the Preferences object.


Context

I'm writing a small Android app with very lightweight use of local storage:

  1. I need to read all settings from local storage at app startup (doesn't need to be blocking though).
  2. As the user interacts with the app, update the local storage. No need to read anymore.

Examples

What finally worked:

val preferences =
            context.transliterationStatesDataStore.data.firstOrNull() ?: return
val preferences =
            context.dataStore.data.map { preferences -> preferences }
                .first()

What I've tried before:

val preferences =
            context.dataStore.data.map { preferences -> preferences }
                .lastOrNull() ?: return
val preferences =
            context.dataStore.data.lastOrNull() ?: return
Log.d("debug", context.dataStore.data.count().toString())

Confusion

I'm confused why:

  1. .count() didn't work (suspended forever)
  2. .lastOrNull() didn't work (suspended forever)
  3. It feels wrong to read the first value of the flow when what I want is the latest version of the Preferences from disk.

There is a related question here but it didn't receive any answers beyond more data points from the author.


Solution

  • A Flow from DataStore is infinite because it monitors the storage in perpetuity so it can emit every time the value changes. Since it's infinite, functions that collect the flow until its final value to get its total count (infinity) or last value (which never comes) will hang forever.

    Flows are cold. When you call first(), then the flow queries the data store to get the current value to emit for collection. It will be the most up-to-date value. first() is the correct way to get the latest value only. If you want to continually react to the latest value as new values are posted, then you can use collect { } instead of first().

    You will find that most Flows are infinite if they monitor something like storage or a database. For example, Flows returned by Room database functions are all infinite, just like the ones from DataStore.