Странное поведение при фильтрации ввода в текстовом поле в Jetpack Compose

Я внедряю Jetpack Compose 1.1.1 в свое существующее приложение для Android, перенося в него действие. В макете XML для этого действия у меня есть EditText, сконфигурированный с android:inputType = "numberDecimal".

В версии Compose у меня теперь есть модель представления, которая содержит TextFieldValue как часть объекта состояния (который наблюдается в составном через StateFlow), и метод, который вызывается в TextFieldonValueChange. Этот метод проверяет, что входные данные могут быть проанализированы как десятичное число, и применяет изменение к объекту состояния, если это так, или сохраняет предыдущее значение, если нет.

Однако я заметил, что иногда ввод, который я получаю в onValueChange, представляет собой предыдущий текст, который был отклонен методом модели представления (вместо текущего значения в объекте состояния), что приводит к отклонению того, что было бы допустимым вводом. Это как если бы TextField сохранял ранее набранный текст в каком-то внутреннем состоянии, даже если мое состояние не было обновлено.

Вот дистилляция соответствующего кода:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MaterialTheme {
                Screen()
            }
        }
    }
}

class MainViewModel: ViewModel() {

    data class State(
        val prompt: String,
        val input: TextFieldValue,
    )

    private val innerState = MutableStateFlow(State(prompt = "Prompt", input = TextFieldValue("")))
    val state = innerState.asStateFlow()

    private val decimalSeparator by lazy { DecimalFormatSymbols.getInstance().decimalSeparator }
    private val decimalMatcher by lazy {
        Pattern.compile("(0|[1-9]\\d*)(${"\\$decimalSeparator"}\\d*)?").matcher("")
    }

    fun setInput(input: TextFieldValue) {
        Log.i("MainViewModel", "Got input: $input")
        val newValue = when {
            input.text.isEmpty() -> {
                // Accept empty string
                input
            }
            decimalMatcher.reset(input.text).matches() -> {
                // Accept input
                input
            }
            else -> {
                // Reject input (apply previous value)
                state.value.input
            }
        }
        Log.i("MainViewModel", "Applying input: $newValue")
        innerState.value = innerState.value.copy(input = newValue)
    }
}

@Composable
fun Screen(viewModel: MainViewModel = viewModel()) {
    val state by viewModel.state.collectAsState()
    Column {
        Text(text = state.prompt)
        TextField(value = state.input, onValueChange = { viewModel.setInput(it) })
    }
}

Если я наберу следующую последовательность символов ['2', '.', '.', '.', '5'], я получу вывод журнала:

I/MainViewModel: Got input: TextFieldValue(text='2', selection=TextRange(1, 1), composition=null)
I/MainViewModel: Applying input: TextFieldValue(text='2', selection=TextRange(1, 1), composition=null)
I/MainViewModel: Got input: TextFieldValue(text='2.', selection=TextRange(2, 2), composition=null)
I/MainViewModel: Applying input: TextFieldValue(text='2.', selection=TextRange(2, 2), composition=null)
I/MainViewModel: Got input: TextFieldValue(text='2..', selection=TextRange(3, 3), composition=null)
I/MainViewModel: Applying input: TextFieldValue(text='2.', selection=TextRange(2, 2), composition=null)
I/MainViewModel: Got input: TextFieldValue(text='2...', selection=TextRange(4, 4), composition=null)
I/MainViewModel: Applying input: TextFieldValue(text='2.', selection=TextRange(2, 2), composition=null)
I/MainViewModel: Got input: TextFieldValue(text='2...5', selection=TextRange(5, 5), composition=null)
I/MainViewModel: Applying input: TextFieldValue(text='2.', selection=TextRange(2, 2), composition=null)

Вместо этого я ожидал, что второй и третий символы . будут отклонены, а когда будет введен 5, текстовое поле будет читать 2.5, но вместо этого 5 также будет отклонено, потому что ввод, который я получаю, это 2...5 (хотя это все еще 2. в моем состоянии).

Кстати, вот несколько вариантов примера кода, который я уже пробовал:

  • замените состояние модели представления на var input by remember { mutableStateOf(TextFieldValue("")) } в MainScreen, выполните фильтрацию непосредственно в лямбде onValueChange: поведение точно такое же
  • используйте простой String вместо TextFieldValue в моем объекте состояния: иногда я получу ожидаемый ввод, но в других случаях я получу повторяющийся ., как и раньше. Еще более странно, если я подожду достаточно долго и снова наберу текст, то получу ожидаемый ввод, что неверно для случая TextFieldValue.

Для меня это выглядит как ошибка, но я подумал, что сначала спрошу здесь, если я каким-то очевидным образом неправильно использую структуру.

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
0
61
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я считаю, что это известная ошибка в Compose. См. эту проблему в системе отслеживания проблем. 22 января 2022 года был коммит, чтобы исправить это, но, возможно, он еще не попал в релиз ...

https://issuetracker.google.com/issues/200577798

Кроме того, эта связанная с этим проблема, которая была исправлена ​​9 февраля 2022 г., может быть еще не выпущена. https://issuetracker.google.com/issues/206656075

Я только что проверил и могу подтвердить, что версия 1.2.0-alpha05 устраняет эту проблему для меня.

Fábio Eduardo Rodella 18.03.2022 18:53

Другие вопросы по теме