Использование ViewModel в качестве единственного источника истины в jetpack compose

#android #view #viewmodel #android-jetpack-compose #android-jetpack-datastore

#Android #Вид #модель представления #android-jetpack-сочинять #android-jetpack-хранилище данных

Вопрос:

допустим, у нас есть модель представления, внутри которой есть значение под названием apiKey. Содержимое этого значения поступает из хранилища данных в виде потока, а затем отображается в виде живых данных. С другой стороны, у нас есть фрагмент под названием SettingsFragment, и мы пытаемся отобразить этот апиКей внутри текстового поля, позволить пользователю изменить его и сразу же сохранить в хранилище данных. Решение, которое я в настоящее время использую, приведено ниже, но проблема в том, что пользовательский интерфейс становится очень медленным и медленным при внесении изменений в текст. Мой вопрос в том, каков наилучший способ реализовать это и при этом иметь единственный источник истины для нашего apiKey?

 class SettingsViewModel() : ViewModel() {   val apiKey = readOutFromDataStore.asLiveData()   fun saveApiKey(apiKey: String) {  viewModelScope.launch(Dispatchers.IO) {  saveToDataStore("KEY", apiKey)  }  } }  /** SettingsFragment **/ ...  @Composable fun ContentView() {  var text = mViewModel.apiKey.observeAsState().value?.apiKey ?: ""   Column() {  OutlinedTextField(  label = { Text(text = "API Key") },  value = text,  onValueChange = {  text = it  mViewModel.saveApiKey(it)  })  } }  

Ответ №1:

Не сохраняйте значение текстового поля в событии onValueChange в хранилище данных при каждом нажатии клавиши, что почти наверняка замедляет вас, особенно если вы используете один и тот же поток. Используйте переменную локального состояния и обновляйте хранилище данных только тогда, когда пользователь перемещает фокус в другое место или сохраняет то, что отображается на экране, с помощью нажатия какой-либо кнопки. Вам также необходимо избегать смешивания потоков пользовательского интерфейса с потоками хранения данных, которые должны находиться в потоке ввода-вывода. Вот одно из возможных решений:

 @Composable fun ContentViewHandler() {  ContentView(  initialText = viewmodel.getApiKey(),  onTextChange = { text -gt;  viewmodel.updateApiKey(text)  }  ) }  @Composable fun ContentView(  initialText: String,  onTextChange: (text: String) -gt; Unit ) {  var text by remember { mutableStateOf(initialText) }   Column() {  OutlinedTextField(  label = { Text(text = "API Key") },  value = text,  onValueChange = {  text = it  },  modifier = Modifier.onFocusChanged {  onTextChange(text)  }  )   // Or add a button and save the text when clicked.  } }  

Комментарии:

1. Я столкнулся с двумя проблемами, используя ваше решение. Во-первых, сохранение значения текстового поля при изменении фокуса может сохранить пустую строку в зависимости от того, когда текстовое поле меняет фокус(например, получение фокуса до того, как было установлено начальное текстовое значение). Так что использование кнопки «Сохранить», вероятно, лучший способ.

2. Ну, вы можете проверить, пуста ли строка перед сохранением, и не утруждать себя ее сохранением. В чем вторая проблема?

3. Вторая проблема заключается в том, что при инициализации текстового значения внутри mutableStateOf, apiKey еще не получил никакого значения из хранилища данных, поэтому на момент первой композиции он равен нулю. Поскольку мы не наблюдаем за apiKey как за состоянием, наша композиция не будет изменена в зависимости от каких-либо изменений, полученных живыми данными.

4. Если ваш текст хранится локально в хранилище данных, вы можете использовать подъем состояния, извлечь текст и передать его в свой состав для получения начального значения. viewmodel.getApiKey() должен просто возвращать обычный текст без управления состоянием. Я обновил код, чтобы продемонстрировать это.

5. Кстати, вам не нужны живые данные для вашего текста. Нет смысла отслеживать изменения данных для текстового поля. Вы просто предоставляете исходные данные, а затем обновляете их при изменении фокуса или при нажатии кнопки.