Skip to content

ChipTextFieldState.textFieldFocusState is not in sync with the UI, causing ChipTextFieldState.focusTextField to fail #174

@qpwoeirut

Description

@qpwoeirut

Environment

I'm using io.github.dokar3:chiptextfield-m3 on version 0.7.0-alpha05. I discovered this issue using Interactive Mode Preview in Android Studio Meerkat | 2024.3.1 Patch 1 (Build #AI-243.24978.46.2431.13208083), but I suspect it would manifest in an actual device as well.

Issue

When the Input component loses focus, textFieldFocusState does not update. This can cause an issue where state.focusTextField() becomes a no-op and does not move focus to the text field as expected.

Reproduction

  1. Add a chip to the input field
  2. Invoke state.focusTextField() to set state.textFieldFocusState to TextFieldFocusState.Focused
  3. Move focus to a different component
  4. Invoke state.focusTextField() again. Since state.textFieldFocusState is already TextFieldFocusState.Focused, the snapshotFlow that uses it will not emit another value.
  5. Trying to type into the input field will no longer work. Instead, the first chip is focused.

Proposed solutions

Would it be feasible to either

  1. update textFieldFocusState whenever the focus changes, or
  2. change the way textFieldFocusState is consumed so that duplicate values are still emitted?

I'm fairly new to Android development, so there might be a better fix.

Code to reproduce

I ran into this issue when building an autocomplete menu that uses ChipTextField. Below is a simplified version. I can share the full code if necessary.

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ChipAutocompleteInputField() {
    val state = rememberChipTextFieldState<Chip>()
    var value by remember { mutableStateOf("") }
    var expanded by remember { mutableStateOf(false) }
    val options = listOf("first", "second", "third")

    Button(onClick = { state.focusTextField() }) {
        Text("Invoke focusTextField")
    }
    ExposedDropdownMenuBox(expanded = expanded, onExpandedChange = { expanded = it }) {
        ChipTextField(
            state = state,
            value = value,
            onValueChange = { value = it },
            onSubmit = ::Chip,
            label = { Text("example") },
            innerModifier = Modifier.menuAnchor(MenuAnchorType.PrimaryEditable),
        )
        ExposedDropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
            options.forEach { option ->
                DropdownMenuItem(
                    onClick = {
                        value = ""
                        expanded = false
                        state.addChip(Chip(option))
                        state.focusTextField()
                    },
                    text = { Text(option) },
                )
            }
        }
    }
}

@Preview(showBackground = true, widthDp = 300, heightDp = 600)
@Composable
fun ChipAutocompleteInputFieldPreview() {
    AppTheme(darkTheme = true) {
        Column {
            ChipAutocompleteInputField()
        }
    }
}

Workaround

Changing the Button's onclick to the following fixes the issue.

MainScope().launch {
    if (state.isTextFieldFocused) {
        state.clearTextFieldFocus()
        awaitFrame() // Changes seem to be ignored without this, I guess it only checks once per frame?
    }
    state.focusTextField()
}

This changes textFieldFocusState so that calling focusTextField will emit a new value, but it's hacky and causes issues if the user is typing quickly.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions