Переменная не обновляется из потока viewModel

Может кто-нибудь сказать мне, почему значение otp не обновляется resetText? Шаги:

  1. введите текст в TextField
  2. нажмите «Сбросить текст»

Это ошибка или какие-то проблемы с моим кодом?

Модель представления

@HiltViewModel
class MyViewModel @Inject constructor() : ViewModel() {
    val state = MutableStateFlow(SomeState())

    fun resetText() {
        state.update { it.copy(text = null) }
    }

    fun changeText() {
        state.update { it.copy(text = Math.random().toString()) }
    }
}

Государственный класс

data class SomeState(
    val text: String? = null,
)

Вид

@Composable
fun SomeView(modifier: Modifier = Modifier) {
    val viewModel = hiltViewModel<MyViewModel>()
    val state by viewModel.state.collectAsState()
    var otp by rememberSaveable(state.text) { mutableStateOf(state.text) }
    Column(
        modifier = modifier
            .padding(horizontal = 16.dp)
            .background(Color.White)
            .fillMaxSize(),
        verticalArrangement = Arrangement.SpaceEvenly,
        horizontalAlignment = Alignment.CenterHorizontally,
    ) {
        TextField(
            value = otp.orEmpty(),
            onValueChange = { otp = it }
        )
        Button(onClick = { viewModel.resetText() }) {
            Text(text = "reset text")
        }
        Button(onClick = { viewModel.changeText() }) {
            Text(text = "Change text")
        }
    }
}

P.S. Я заставил это работать, изменив resetText на

fun resetText() {
    viewModelScope.launch {
        state.update { it.copy(text = "") }
        delay(100)
        state.update { it.copy(text = null) }
    }
}

Но это явно плохой вариант.

2
0
113
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

Я предполагаю, что вы видите, что когда вы вводите текст вручную, он не сбрасывается при вызове метода сброса. Это связано с тем, что вы сбрасываете модель, а не локальную копию данных в otp. Это в целом нарушает принцип единого владельца государства, поскольку вводит копию, которую затем необходимо синхронизировать вручную.

Рассмотрите возможность использования только одной копии данных. Например,

@Composable
fun SomeView(modifier: Modifier = Modifier) {
    val viewModel = hiltViewModel<MyViewModel>()
    val state by viewModel.state.collectAsState()
    Column(
        modifier = modifier
            .padding(horizontal = 16.dp)
            .background(Color.White)
            .fillMaxSize(),
        verticalArrangement = Arrangement.SpaceEvenly,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        TextField(
            value = state.text.orEmpty(),
            onValueChange = { viewModel.updateText(it) }
        )
        Button(onClick = { viewModel.resetText() }) {
            Text(text = "reset text")
        }
        Button(onClick = { viewModel.changeText() }) {
            Text(text = "Change text")
        }
    }
}

В качестве альтернативы рассмотрите возможность обновления копии состояния при сбросе модели.

        Button(onClick = {
            viewModel.resetText()
            otp = "" // Also reset the local copy of the state
        }) {
            Text(text = "reset text")
        }

Обратите внимание, что значение otp никуда не денется. В этом случае вам потребуется добавить действие «Отправить» или «Применить», которое обновит модель.

С вашим потоком происходит следующее:

  1. Значение инициализируется с помощью null при создании экземпляра модели представления.
  2. Пользователь меняет текст в пользовательском интерфейсе, который обновляется otp. Значение потока остается неизменным, поэтому оно по-прежнему равно нулю.
  3. Кнопка нажимается и вызывается resetText. Значение потока установлено на null, но поскольку оно уже было нулевым, поток не интерпретирует это как изменение и не уведомляет свои сборщики, поэтому в вашем компонуемом объекте ничего не происходит.

Если вы хотите сбросить текстовое поле, вам следует установить otp на ноль и не менять состояние:

Button(onClick = { otp = null }) {
    Text(text = "reset text")
}

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

Может ли сопрограмма, запущенная через runBlocking, выполняться в двух или более потоках?
Как сопоставить тип базы данных массива с Kotlin
Как обрабатывать запросы на увольнение из диалога, запущенного из Composable, переданного как переменная
Как исправить проблему с KMP. Инициализируйте хотя бы одну цель Kotlin в «composeApp (:composeApp)» или «shared»?
Плагин [id: 'kotlin-android', версия: '1.7.10'] не был найден ни в одном из следующих источников в Android Studio для Flutter
Текст с обводкой или границами в наборе реактивного ранца
Ошибка Kotlin: java.lang.RuntimeException: не удалось найти реализацию для com.example.tutorialfollowing.AppDatabase. AppDatabase_Impl не существует
Ошибка размера анимированной бутылки с водой при составлении реактивного ранца
Рукоять кинжала: «Невозможно создать экземпляр ViewModel» при использовании внедрения зависимостей
Невозможно получить доступ к классу Retrofit2.Response. Проверьте путь к классам вашего модуля на предмет отсутствующих или конфликтующих зависимостей