Having a composable wich haves a textfield which n...
# compose
p
Having a composable wich haves 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:
Copy code
userPreferencesRepository.searchText
This is the datastore value code, in my datastore repo class:
Copy code
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] ?: ""
    }
k
Hi Pablo, You repository code is fine. But you need to expose the searchText Flow from the ViewModel. Something like this:
Copy code
class MyViewModel(private val userPreferencesRepository: UserPreferencesRepository) : ViewModel() {
    val searchTextFlow: Flow<String> = userPreferencesRepository.searchText
}
Now Collect the
Flow
inside your Composable using
collectAsStateWithLifecycle
Copy code
@Composable
fun MyTextField(viewModel: MyViewModel) {
    // Collect the searchText Flow as a state
    val searchText by viewModel.searchTextFlow.collectAsStateWithLifecycle(initialValue = "")

    var text by remember { mutableStateOf(searchText) }
Copy code
TextField(
        value = text,
        onValueChange = { newText ->
            text = newText
            // You can add code here to save the newText to DataStore if needed
        },
        label = { Text("Search") }
    )
}
p
It is not giving compilation errors now, but something is not working, because when I close the app, and reopen it, the stored value is not recovered, instead is returned "". I'm storing the value in datastore everytime the field is changed, I checked it with breakpoings. This is being called:
Copy code
fun updateSearchText(searchText: String) {
    viewModelScope.launch {
        userPreferencesRepository.saveSearchTextPreference(searchText)
    }
}
which calls to this in the datapreferences repo:
Copy code
suspend fun saveSearchTextPreference(searchText: String) {
    dataStore.edit { preferences ->
        preferences[SEARCH_TEXT] = searchText
    }
}
ahhh I see that this part of the datastore searchText variable obtaining code is being called after the screen has been displayed and after the composable with the field has been already painted with empty value:
Copy code
.map { preferences ->
    preferences[SEARCH_TEXT] ?: ""
}
And it is recovering the stored value, but it's too late, because the composable has been already presented with empty text. Why?
k
You can do another way:
Copy code
class UserPreferencesRepository(context: Context) {
    private val dataStore: DataStore<Preferences> = context.createDataStore(name = "user_preferences")

    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] ?: ""
        }

    suspend fun saveSearchTextPreference(searchText: String) {
        dataStore.edit { preferences ->
            preferences[SEARCH_TEXT] = searchText
        }
    }
}

// MainViewModel:
class MyViewModel(private val userPreferencesRepository: UserPreferencesRepository) : ViewModel() {
    val searchTextFlow: Flow<String> = userPreferencesRepository.searchText

    fun updateSearchText(searchText: String) {
        viewModelScope.launch {
            userPreferencesRepository.saveSearchTextPreference(searchText)
        }
    }
}

// COmposabe
@Composable
fun MyTextField(viewModel: MyViewModel) {
    // Collect the searchText Flow as a state
    val searchText by viewModel.searchTextFlow.collectAsStateWithLifecycle(initialValue = "")

    var text by remember { mutableStateOf(searchText) }

    // TextField to display and edit the text
    TextField(
        value = text,
        onValueChange = { newText ->
            text = newText
            viewModel.updateSearchText(newText)  // Save the new text to DataStore
        },
        label = { Text("Search") }
    )
}
p
what have you changed?
everything in the code seems equal
k
I just sent a wrong code
Copy code
@Composable
fun MyTextField(viewModel: MyViewModel) {
    val searchText by viewModel.searchTextFlow.collectAsState(initial = "")

    var text by rememberUpdatedState(searchText)

    LaunchedEffect(Unit) {
        viewModel.searchTextFlow.collect { newText ->
            text = newText
            Log.d("Composable", "Text state updated to: $newText")
        }
    }

   
    TextField(
        value = text,
        onValueChange = { newText ->
            text = newText
            viewModel.updateSearchText(newText)
        },
        label = { Text("Search") }
    )
}
Try to use LaunchEffect instead. I think, this will solve your problem.
p
are you trying this code? maybe are you using GPT or something? it doesn't work too
it even gives compilation error
Type 'State<String>' has no method 'setValue(Nothing?, KProperty<*>, String)' and thus it cannot serve as a delegate for var (read-write property)
don't let me import anything more, I supposedly have all the imports
also I consider very complex to use a LaunchedEffect to get a value from a flow and initialize a field
don't sure this is the way to solve this
k
No Mate. I'm doing my stuff alongside.
In one of my latest projects, I faced same issue but with the chatbot. I resolved this way and I provided that code reference to you.
Acutally, The issue is here. I think, its passing a null value:
Copy code
.map { preferences ->
    preferences[SEARCH_TEXT] ?: ""
}
p
that line is passing the correct stored value "aa" in the text, but that method is being called some seconds after the composable has been presented in the screen
k
Kindly check the composition using Layout Inspector.
p
well that doesn't solve anything, thank you bytheway
I'm hoping someone can give another possible solution for this issue