Jetpack Compose MutableLiveData не обновляет компоненты пользовательского интерфейса
Я пытаюсь отобразить сразу несколько индикаторов выполнения загрузки через список объектов данных, содержащих идентификатор загрузки и значение выполнения. Значения этого списка объектов обновляются нормально (отображается в журнале), но компоненты пользовательского интерфейса НЕ БУДУТ обновляться после того, как их начальное значение изменится с нуля на первое значение прогресса. Пожалуйста помоги!
Я вижу, что есть похожие вопросы, но их решения не работают для меня, включая прикрепление наблюдателя.
class DownLoadViewModel() : ViewModel() {
...
private var _progressList = MutableLiveData<MutableList<DownloadObject>>()
val progressList = _progressList // Exposed to the UI.
...
//Update download progress values during download, this is called
// every time the progress updates.
val temp = progress.value
temp?.forEach { item ->
if (item.id.equals(download.id)) item.progress = download.progress
}
progress.postValue(temp)
...
}
Компонент пользовательского интерфейса
@Composable
fun ExampleComposable(downloadViewModel: DownloadViewModel) {
val progressList by courseViewModel.progressList.observeAsState()
val currentProgress = progressList.find { item -> item.id == local.id }
...
LinearProgressIndicator(
progress = currentProgress.progress
)
...
}
4 ответа
Я искал много текста, чтобы решить проблему, заключающуюся в том, что List в ViewModel не обновляет Composable. Я пробовал три способа, но безрезультатно, например: LiveData, MutableLiveData, mutableStateListOf, MutableStateFlow.
По тесту обнаружил, что значение изменилось, но интерфейс не обновляется. В документе говорится, что страница будет обновляться только при изменении значения State. Основная проблема - это проблема с данными. Если он не обновлен, это означает, что государство не отслеживало обновление данных.
Вышеупомянутые методы эффективны для добавления и удаления, но одно обновление не работает, потому что я обновляю элемент в T, но объект не изменился.
Решение - глубокое копирование.
fun agreeGreet(greet: Greet) {
val g = greet.copy(agree = true) // This way is invalid
favourites[0] = g
}
fun agreeGreet(greet: Greet) {
val g = greet.copy() // This way works
g.agree = true
favourites[0] = g
}
Очень странно, потрачено много времени, надеюсь, будет полезно тем, кому нужно обновиться.
Совершенно нормально работать с LiveData / Flow вместе с Jetpack Compose. Фактически, они явно указаны в документации .
Эти же документы также описывают вашу ошибку несколькими строками ниже в красном поле:
Внимание! Использование изменяемых объектов, таких как ArrayList или mutableListOf() в качестве состояния в Compose, приведет к тому, что ваши пользователи увидят неверные или устаревшие данные в вашем приложении.
Изменяемые объекты, которые не наблюдаются, такие как ArrayList или изменяемый класс данных, не могут быть замечены Compose для запуска перекомпоновки при их изменении.
Вместо использования ненаблюдаемых изменяемых объектов мы рекомендуем использовать наблюдаемый держатель данных, такой как State<List> и неизменяемый listOf().
Итак, решение очень простое:
- сделай свой
progressList
неизменный - при обновлении создайте новый список, который является копией старого, но с вашими новыми значениями прогресса
Насколько это возможно, рассмотрите возможность использования
mutableStateOf(...)
в JC вместо
LiveData
и
Flow
. Итак, внутри вашего
viewmodel
,
class DownLoadViewModel() : ViewModel() {
...
private var progressList by mutableStateOf(listOf<DownloadObject>()) //Using an immutable list is recommended
...
//Update download progress values during download, this is called
// every time the progress updates.
val temp = progress.value
temp?.forEach { item ->
if (item.id.equals(download.id)) item.progress = download.progress
}
progress.postValue(temp)
...
}
Теперь, если вы хотите добавить элемент в
progressList
, вы можете сделать что-то вроде: -
progressList = progressList + listOf(/*item*/)
В своей деятельности
@Composable
fun ExampleComposable(downloadViewModel: DownloadViewModel) {
val progressList by courseViewModel.progressList
val currentProgress = progressList.find { item -> item.id == local.id }
...
LinearProgressIndicator(
progress = currentProgress.progress
)
...
}
Да, я знаю, что это не лучшая реализация, но лично мне помогло:
Фрагмент:
class Fragment : Fragment() {
private val viewModel : MyViewModel by viewModels()
private val flow by lazy {
callbackFlow<List<Item>> {
viewModel.viewModelItmes.observe(viewLifecycleOwner) {
trySend(it)
}
awaitClose()
}
}
...
@Composable private fun Root() {
val items = remember { mutableStateListOf<ChatListItem>() }
LaunchedEffect(Unit) {
flow.collect {
items.clear()
items.addAll(it)
}
}
Column {
items.forEach { /* ... */ }
}
}
}
Модель просмотра:
class MyViewModel : ViewModel() {
val viewModelItmes = MutableLiveData<ArrayList<Item>>(emptyList())
fun someOneAddItem(item : Item) {
viewModelItmes.value!!.add(item)
viewModelItmes.postValue(viewModelItmes.value!!)
}
}