#android #android-fragments #kotlin-coroutines #android-viewmodel
Вопрос:
TL;DR
Для одного экземпляра фрагмента, который добавляется/удаляется снова и снова, viewModelScope
в его ViewModel работает только до тех пор, пока фрагмент не будет извлечен в первый раз. После этого viewModelScope
становится неактивным. Как я могу повторно инициализировать viewModelScope
, когда я добавляю фрагмент обратно? Или мне следует поискать лучшую реализацию, чтобы она оставалась активной?
Сценарий:
Я использовал RxJava
through ViewModel
для извлечения данных из сети в старом проекте и для отправки обновлений в пользовательский интерфейс, я использовал LiveData.
Ниже показано, как я инициализировал типичную модель представления внутри фрагмента:
private val fruitsViewModel by viewModels<FruitsViewModel>()
Ниже приведена вышеупомянутая модель представления:
class FruitsViewModel: ViewModel {
private var category: String = FruitCategories.DEFAULT
private val _fruits = MutableLiveData<List<Fruit>>()
fun fruits(): LiveData<List<Fruit>> = _fruits
fun updateCategory(newCategory: String) {
this.category = newCategory
fetchFruits()
}
fun fetchFruits() { ... }
}
В «Фрагменте onViewCreated()
» я позвонил fruitsViewModel.fetchFruits()
. Это отлично сработало(без каких-либо видимых проблем).
Сегодня я переключил конкретный код RxJava с сопрограммами для извлечения сетевых данных в моей модели просмотра. Что-то вроде этого:
fun fetchFruits() {
/* this.fruitsJob = */
viewModelScope.launch {
...
}
}
Это тоже хорошо работает, даже после того, как я заменяю фрагмент и возвращаюсь к нему. Но, если я сохраню экземпляр фрагмента, объявленный в действии, и буду использовать его снова, viewModelScope.launch {}
он больше не будет работать. Что-то вроде этого:
class FruitsActivity: AppCompatActivity() {
private val fruitsFragment = FruitsFragment()
private fun showFruitsFragment() {
supportFragmentManager
.beginTransaction()
.replace(getFragmentContainerId(), fruitsFragment)
.addToBackStack(fruitsFragment.tag)
.commit()
}
private fun removeFragments() {
val manager = supportFragmentManager
if (manager.backStackEntryCount > 0) {
val first: FragmentManager.BackStackEntry = manager.getBackStackEntryAt(0)
manager.popBackStack(first.id, FragmentManager.POP_BACK_STACK_INCLUSIVE)
}
}
}
Причина сохранения экземпляра фрагмента заключается в том, чтобы загрузить данные API только один раз, и после этого они должны просто появляться по нажатию кнопки без какого-либо мерцания/загрузки.
После того , как я позвоню removeFragments()
, fruitsViewModel
уже позвонила ее onCleared()
для сохраненной декларации fruitsFragment
. Таким образом, он viewModelScope
становится неактивным, и если я fruitsFragment
снова добавлю его , позвонив showFruitsFragment()
, это больше не будет извлекать сетевые данные. Это не имело отношения к моей реализации Rxjava, потому что я ее не использовал viewModelScope
.
Я думаю, что могу просто решить проблему, связавшись fruitsViewModel
FruitsActivity
вместо этого с экземпляром. Но я хочу знать, можно ли считать это лучшим способом.
Комментарии:
1.
The reason for keeping the fragment instance is to load API data only once
итак, похоже, вам нужна модель общего представления, не так ли ? почему бы не прикрепить модель представления к действию и не заставить ее выполнять вызовы служб по мере необходимости2. Согласен с @a_local_nobody . Поскольку вы сохраняете фрагмент дольше (аналогично сроку действия), независимо от того, сколько раз он присоединялся и отсоединялся, лучше использовать область действия или модель представления действия.
3. Кстати, поскольку вы хотите сохранить загруженные данные независимо от жизненного цикла фрагмента, данные должны храниться в действии, а не во фрагменте. ИМО, хранение экземпляров фрагментов не является хорошей практикой.
Ответ №1:
Да, вы должны viewModel
по-другому оценивать свои возможности, в вашем случае определение области fruitsViewModel
FruitsActivity
имеет полный смысл.
Я не уверен, как RxJava
работал ваш код, не видя кода, я предполагаю, что это как-то связано с тем disposed
, что он был неправильным.
Кроме того, даже если вы создадите область, которая переживет viewModelScope
(например, создание и поддержание a CoroutineScope
в деятельности), моя главная проблема заключается в том, что подход не является интуитивным/естественным, людям будет сложнее проверить, как и где это CoroutineScope
поддерживается
Комментарии:
1. Согласен, спасибо. p.s. реализация, связанная с RxJava, утилизировала все расходные
onCleared()
материалы методом ViewModel. Это позволяет создавать новые вызовы после onCleared (), поскольку RxJava не связана ни с каким жизненным циклом. Но для сопрограмм я зависим отviewModelScope
того, что связано с жизненным циклом(?).2. Хм, интересно, чем отличается реализация, потому что технически
viewModelScope
отменяет сопрограмму вonCleared()
3. Реализация RxJava не зависела от сопрограмм или
viewModelScope
.fetchFruits()
это было так же, как метод в любом другом классе.