Reputation: 11035
When using Jetpack composable, sometime it needs to use TexField to take user's input. And the value of the input field change should trigger recomposition. But also it does not want the recomposition to happen on every char typed in.
is there a suggested way to avoid the unnecessary recomposition caused by the text field input's change?
Find a way using debounce for the text field, see the simplified code snippet below. But believe there should be an idiomatic way to do debounce for a text field. Does anyone have suggestion?
@Composable
fun TestScreen() {
// Main states that drive recomposition, it will be bound in the composable for triggering the recomposition.
var authAnswer by rememberSaveable { mutableStateOf("") }
// Intermediate states for capturing immediate input changes
var tempAnswer by remember { mutableStateOf(authAnswer) }
// Debounce each input field
debounceInput(tempAnswer) { debouncedValue -> authAnswer = debouncedValue }
Column(modifier = Modifier.fillMaxSize()) {
MyTextField(
labelValue = "Answer",
onTextChanged = { tempAnswer = it }, // Update only the intermediate state
value = tempAnswer // Bind the intermediate state
)
// other components ...
}
}
@Composable
fun debounceInput(
input: String,
delayMillis: Long = 300L,
onDebouncedChange: (String) -> Unit
) {
LaunchedEffect(input) {
delay(delayMillis)
onDebouncedChange(input)
}
}
Upvotes: 1
Views: 53
Reputation: 67293
You can use snapshotFlow with debounce operator to have debounce change only after user stops typing after the specified delay but to prevent unnecessary recompositions you should also check scoped recomposition and maybe stability as well.
@Preview
@Composable
fun TestScreen() {
// Main states that drive recomposition, it will be bound in the composable for triggering the recomposition.
var authAnswer by rememberSaveable { mutableStateOf("") }
// Intermediate states for capturing immediate input changes
val tempAnswer = remember { mutableStateOf(authAnswer) }
// Debounce each input field
debounceInput(tempAnswer) { debouncedValue -> authAnswer = debouncedValue }
Column(modifier = Modifier.fillMaxSize()) {
TextField(
onValueChange = {
tempAnswer.value = it
}, // Update only the intermediate state
value = tempAnswer.value // Bind the intermediate state
)
// other components ...
}
}
@Composable
fun debounceInput(
input: State<String>,
delayMillis: Long = 300L,
onDebouncedChange: (String) -> Unit
) {
LaunchedEffect(Unit) {
snapshotFlow { input.value }
.debounce(delayMillis)
.filter { it.isNotEmpty() }
.collect {
println("Collected ${it}")
onDebouncedChange(it)
}
}
}
Upvotes: 1