How to detect if the user stops writing to a TextField?

Viewed 1243

I have a screen with Jetpack Compose in which I have a TextField for the user to write a text.

With this text I will make a query to obtain data. I want this query to be made when the user finishes typing.

Is there a way to know if the user takes 2 seconds without writing (for example) to launch this query?

2 Answers

To query after 2 seconds after user stop typing, I think you can use debounce operator (similar idea to the answer here Jetpack Compose and Room DB: Performance overhead of auto-saving user input?)

Here is an example to handle text change on TextField, then query to database and return the result to dbText

class VM : ViewModel() {
    val text = MutableStateFlow("")
    val dbText = text.debounce(2000)
        .distinctUntilChanged()
        .flatMapLatest {
            queryFromDb(it)
        }

    private fun queryFromDb(query: String): Flow<String> {
        Log.i("TAG", "query from db: " + query)
        if (query.isEmpty()) {
            return flowOf("Empty Result")
        }
        // TODO, do query from DB and return result
    }
}

In Composable

Column {
    val text by viewModel.text.collectAsState()
    val dbText by viewModel.dbText.collectAsState("Empty Result")

    TextField(value = text, onValueChange = { viewModel.text.value = it })
    Text(text = dbText)
}

Another way is to avoid the viewmodel completely. Utilise the LaunchedEffect that will cancel/restart itself on every key (text) change. I find this to be way cleaner than to couple your debounce code to your viewmodel.

@Composable
private fun TextInput(
    dispatch: (ViewModelEvent) : Unit,
    modifier: Modifier = Modifier
) {

    var someInputText by remember { mutableStateOf(TextFieldValue("")) }
    ...

    TextField(
        value = someInputText,
        onValueChange = {
            someInputText = it
        },
    )
    ...

    LaunchedEffect(key1 = someInputText, block = {
        // this check is optional if you want the value to emit from the start
        if (someInputText.text.isBlank()) return@LaunchedEffect

        delay(2000)
        // print or emit to your viewmodel
        dispatch(SomeViewModelEvent(someInputText.text))
    })
}
Related