For the moment I use this approach in my ViewModel:
private val _email = MutableStateFlow("")
val email: StateFlow<String> = _email.asStateFlow()
fun updateEmail(email: String) {
_email.value = email
}
And inside the UI, I use:
val email by viewModel.email.collectAsStateWithLifecycle()
OutlinedTextField(
value = "",
onValueChange = { newEmail ->
viewModel.updateEmail(newEmail)
},
label = {
Text("Email")
}
)
But now I discovered that I can use directly inside my UI:
val email = rememberTextFieldState()
OutlinedTextField(
state = email,
label = {
Text("Email")
}
)
Without any need of using the ViewModel. No onValueChange
, no need to collect any state, no need to explicitly place the cursor at the end of the text if needed. Is this approach correct? Is it better to use rememberTextFieldState
over MutableStateFlow
?
Please never use asynchronous data structures like a Flow to update a TextField while typing. That doesn't properly work. See this blog entry for an in-depth explanation why: https://medium.com/androiddevelopers/effective-state-management-for-textfield-in-compose-d6e5b070fbe5
The TextField API underwent multiple changes over time, with each change introducing a new overload to the function so you can still use the older variants. In the current alpha of Material3 1.4.0 (which you apparently are using) there are three variants of (Outlined)TextField:
value
parameter of type String
and an onValueChange
callback.value
property of type TextFieldValue
and an onValueChange
callback. The TextFieldValue also contains the current text selection, if any.state
property of type TextFieldState
and no callbacks anymore.The main advantage of the last one is that all updates to the TextField are now handled internally. You don't need to do that yourself anymore, which effectively solves the issue with asynchronous updates that I mentioned above. It is also easier to use (you don't need to provide an onValueChange
callback), so you should always prefer this one over the other variants.
For the sake of completenes, although you don't seem to need it (right now):
Without the callback you cannot react to each single change anymore the way it was previously possible, f.e. for a list of search suggestions when the TextField is used as a search box. When using a TextFieldState
you can instead access it's text
property (which is backed by a MutableState) and perform logic on it whenever it changes, leveraging Compose's recomposition logic. You can also convert it to a Flow by using snapshotFlow { textFieldState.text }
, then you can even move the state to the view model and observe it there for changes or integrate it with other flows.
And secondly, without a callback you cannot modify the input anymore before you set the new value of the TextField, for example if you want to limit the length of the text. The solution is to pass an InputTransformation
to the TextField that changes the text that was input before it is stored in the internal state and displayed in the UI. That is a functional interface that you can implement yourself, but for some common tasks there are already default implementations, like limiting the length with InputTransformation.maxLength(10)
.