lannyf
lannyf

Reputation: 11035

what is the suggested way to avoid unnecessary recomposition caused by the text filed value change

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

Answers (1)

Thracian
Thracian

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

Related Questions