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.
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 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()
}
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
}
}