Я внедряю Jetpack Compose 1.1.1 в свое существующее приложение для Android, перенося в него действие. В макете XML для этого действия у меня есть EditText
, сконфигурированный с android:inputType = "numberDecimal"
.
В версии Compose у меня теперь есть модель представления, которая содержит TextFieldValue
как часть объекта состояния (который наблюдается в составном через StateFlow
), и метод, который вызывается в TextField
onValueChange
. Этот метод проверяет, что входные данные могут быть проанализированы как десятичное число, и применяет изменение к объекту состояния, если это так, или сохраняет предыдущее значение, если нет.
Однако я заметил, что иногда ввод, который я получаю в 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
.Для меня это выглядит как ошибка, но я подумал, что сначала спрошу здесь, если я каким-то очевидным образом неправильно использую структуру.
Я считаю, что это известная ошибка в Compose. См. эту проблему в системе отслеживания проблем. 22 января 2022 года был коммит, чтобы исправить это, но, возможно, он еще не попал в релиз ...
https://issuetracker.google.com/issues/200577798
Кроме того, эта связанная с этим проблема, которая была исправлена 9 февраля 2022 г., может быть еще не выпущена. https://issuetracker.google.com/issues/206656075
Я только что проверил и могу подтвердить, что версия
1.2.0-alpha05
устраняет эту проблему для меня.