Reputation: 37721
Having a composable which has a textfield
which needs to work with a by remember mutablestateof String
called text
, that variable must be initialized with a datastore
stored value. I have the datastore
repo on the viewmodel
, but it returns a Flow
, which also needs to be executed in a coroutine. So... I'm stuck.
I can't return directly the Flow
and do collectAsStateWithLifecycle
because it needs a coroutine, and I can't return the coroutine because it returns a job and I need a string. How to solve this?
This is my datastore variable with the initial value for the field, and this is in my viewmodel:
userPreferencesRepository.searchText
This is the datastore value code, in my datastore repo class:
val searchText: Flow<String> = dataStore.data
.catch {
if (it is IOException) {
Log.e(TAG, "Error reading preferences.", it)
emit(emptyPreferences())
} else {
throw it
}
}
.map { preferences ->
preferences[SEARCH_TEXT] ?: ""
}
A sample of a textfield that should receive that text variable from viewmodel, but I don't know how to get it.
viewModel: FlightsScreenViewModel = viewModel(factory = FlightsScreenViewModel.factory)
var text by remember{ mutableStateOf("") } //This should be initialized with the text from the datastore of the viewmodel
TextField(
value = text,
onValueChange = { newText ->
text = newText
},
label = { Text("Search") }
)
Upvotes: 0
Views: 165
Reputation: 1
You can try to assign your seachText to text variable when it is initialized with value from database. The idea is to assign value to text variable when the view is recomposed. You will need another variable searchTextLoaded for this.
In your viewModel class remove runBlocking and add function saveSearchTextToPreferences:
fun getSearchTextFromPreferences() = userPreferencesRepository.searchText
fun saveSearchTextToPreferences(text: String) {
viewModelScope.launch {
userPreferencesRepository.saveSearchText(text)
}
}
In your composable:
val searchText = viewModel.getSearchTextFromPreferences().collectAsState("").value
var text by remember { mutableStateOf("") }
var searchTextLoaded by remember { mutableStateOf(false) }
if (searchText.isNotEmpty() && !searchTextLoaded) {
text = searchText
searchTextLoaded = true
}
TextField(
value = text,
onValueChange = { newText ->
text = newText
viewModel.saveSearchTextToPreferences(newText)
},
label = { Text("Search") }
)
Here is link to complete app code on github: https://github.com/alba221/flightsearch
Upvotes: 0
Reputation: 37721
Well, using a flow was not the correct approach, because it initialized the textfield with empty "" string, and 1 or 2 seconds after that, asynchronously, the datastore returned the value, but with the composable already painted with empty string.
I solved it doing this in the viewmodel:
fun getSearchTextFromPreferences() = runBlocking {
userPreferencesRepository.searchText.first()
}
and this in the composable:
val initialText = viewModel.getSearchTextFromPreferences()
var searchText by remember{ mutableStateOf(initialText) }
I had to use runBlocking to force datastore work syncronously with compose, forcing it to wait until get the value before painting the textfield. A better option whould be to paint a loading image until the value is returned, and then paint the textfield with the initial value correctly, I'll try that.
Upvotes: 0