androidkotlinandroid-jetpack-composeandroid-jetpack-datastore

How to Get Live Data from Preference Data Store


I have a datastore which is holding a list of locations

Here is the code

private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "location_list")
private val dataStoreKey = stringSetPreferencesKey(LOCATION_KEY)
suspend fun saveLocation(context: Context, data: String) {
    val locationSet = getLocations(context).first().toMutableSet()
    val isAdd = locationSet.add(data)
    if (isAdd) {
        val newSet = locationSet.toSet()
        context.dataStore.edit { preference ->
            preference[dataStoreKey] = newSet
        }
    }
}

suspend fun getLocations(context: Context): Flow<Set<String>> {
    val preferences = context.dataStore.data.first()
    if (preferences[dataStoreKey] != null) {
        return flowOf(preferences[dataStoreKey]!!)
    }
    return flowOf(emptySet())
}

This list is used to get weather data from the API

Here is the code to get weather data

fun getWeatherData(context: Context) {
    getCurrentLocation(context) {currentLocation ->
        val weatherData = mutableListOf<Deferred<WeatherDataDaily>>()
        _weatherState.value.isLoading = true
        viewModelScope.launch {
            citiesRepository.getLocations(context).collect {
                val locationList = it.toMutableList()
                locationList.add(0, currentLocation.getLocationValue())
                for (value in locationList) {
                    val data = async {
                        repository.getWeatherData(value)
                    }
                    weatherData.add(data)
                }
                _weatherState.value = WeatherState(
                    weatherData.awaitAll(),
                    isInitialize = true
                )
            }
        }
        _weatherState.value.isLoading = false
        Log.d(TAG, "getWeatherData: ")
    }
}

I want the viewmodel to know the change in datastore and get new data for the _weatherState when I add a location into the datastore

It's a lot of code to show here, so please have a look at my project WeatherApp

I'm looking forward to any advice or suggestions. Thank you.


Solution

  • Through my examples below, I'm using the ?: operator to provide defaults when retrieving from the store. This is much more idiomatic and simpler than doing a separate property and then checking if null and then having to use the scary !!.

    Your problem with working with Flows

    Your getLocations function has some incorrect usage of Flows. It makes no sense to create a Flow of a single item. Why take a flow and reduce it to only one item, but then re-wrap it in another flow? You could have just mapped the flow.

    suspend fun getLocations(context: Context): Flow<Set<String>> {
        return context.dataStore.data.map { it[dataStoreKey] ?: emptySet() }
    }
    

    If you really want a function that returns a current snapshot value, then first() is appropriate, but you should not be returning a Flow! Then it would look like:

    suspend fun getLocationsSnapshot(context: Context): Set<String> {
        return context.dataStore.data.first()[dataStoreKey] ?: emptySet()
    }
    

    Your problem with writing to the store

    You're leaving open the possibility of your data getting out of sync because you are not editing it atomically. The edit function already provides you the current state of the preferences, and if you do all your work inside the edit lambda, it will be atomic, so you should change saveLocation as follows:

    suspend fun saveLocation(context: Context, data: String) {
        context.dataStore.edit { preference ->
            val locationSet = (preference[dataStoreKey] ?: emptySet())
                .toMutableSet()
            val isAdd = locationSet.add(data)
            if (isAdd) {
                preference[dataStoreKey] = locationSet
            }
        }
    }
    

    or, the same logic more simply:

    suspend fun saveLocation(context: Context, data: String) {
        context.dataStore.edit { preference ->
            preference[dataStoreKey] = (preference[dataStoreKey] ?: emptySet()) + data
        }
    }