Без всей среды мне действительно сложно помочь вам отладить все это, но я рад предоставить вам пару вещей, которые на первый взгляд кажутся немного странными.
Прежде всего all Я бы избегал управления всеми CoroutinesScopes и жизненными циклами самостоятельно, и это легко сделать неправильно. Так что я бы положился на то, что команда Android уже сделала. Взгляните на здесь , это действительно легко установить и использовать. Отличный опыт разработки.
Размещение Deferred
на LiveData
и ожидание на стороне просмотра похоже на запах кода ...
Что, если есть сеть ошибка? Это приведет к возникновению исключения или исключения отмены.
Что, если задача уже была выполнена и вызывает некоторую проблему согласованности пользовательского интерфейса? Это пара крайних случаев, с которыми я бы не хотел справляться.
Просто обратите внимание на LiveData
, поскольку это его основная цель: это держатель значения, и он предназначен для того, чтобы пережить несколько событий жизненного цикла в Fragment
. Итак, как только представление воссоздано, значение готово в LiveData внутри ViewModel.
Ваша функция lazyDeferred
довольно умна, но в мире Android она также опасна. Эти сопрограммы не живут внутри какой-либо области действия, управляемой жизненным циклом, поэтому у них действительно высока вероятность утечки информации. И поверьте мне, вы не хотите утечки каких-либо сопрограмм, поскольку они продолжают свою работу даже после разрушения модели представления и фрагмента, чего вы определенно не хотите.
Все это легко исправить с помощью зависимости Я уже упоминал, что я вставлю сюда еще раз
Вот отрывок о том, как вы можете использовать эти утилиты в своей ViewModel, чтобы убедиться, что жизненный цикл вещей и сопрограммы не вызывают issues:
class WeatherViewModel(
private val forecastRepository: ForecastRepository,
context: Context
) : ViewModel() {
private val appContext = context.applicationContext
// Retrieve the sharedPrefs
val preferences:SharedPreferences
get() = PreferenceManager.getDefaultSharedPreferences(appContext)
// This will run only when currentWeatherData is called from the View
val currentWeatherData = liveData {
val task = viewModelScope.async { forecastRepository.getCurrentWeather() }
emit(task.await())
}
val hourlyWeatherEntries = liveData {
val task = viewModelScope.async { forecastRepository.getHourlyWeather() }
emit(task.await())
}
val weeklyWeatherEntries = liveData {
val task = viewModelScope.async {
val currentDateEpoch = LocalDate.now().toEpochDay()
forecastRepository.getWeekDayWeatherList(currentDateEpoch)
}
emit(task.await())
}
val weatherLocation = liveData {
val task = viewModelScope.async(Dispatchers.IO) {
forecastRepository.getWeatherLocation()
}
emit(task.await())
}
}
При использовании следующего подхода все сетевые вызовы выполняются параллельно, и все они привязаны к viewModelScope
без написания ни одной строки, описывающей жизнь CoroutineScope. Когда ViewModel умирает, область видимости умирает. при повторном создании представления подпрограммы не будут выполняться дважды, и значения будут готовы к чтению.
Что касается конфигурации диаграммы: я настоятельно рекомендую вам настроить диаграмму, как только вы создадите вид, поскольку они сильно связаны между собой. Конфигурация - это то, что вы хотите сделать только один раз и может вызвать визуальные ошибки, если некоторые инструкции выполняются более одного раза (что, я считаю, может происходить с вами), просто так, потому что у меня были проблемы с MP Android с использованием круговой диаграммы.
Подробнее на диаграмме: все логики c построения LineData
лучше бы размещать в фоновом потоке и отображать через LiveData на стороне ViewModel, как если бы вы делали все другое
val property = liveData {
val deferred = viewModelScope.async(Dispatchers.Default) {
// Heavy building logic like:
createChartData()
}
emit(deferred.await())
}
Pro Kotlin совет: Избегайте повторений во время этих длинных функций конфигурации MP Android.
Вместо:
view.configureThis()
view.configureThat()
view.enabled = true
Сделайте:
view.apply {
configureThis()
configureThat()
enabled = true
}
Мне грустно, что я могу просто дать вам эти подсказки и не могу точно определить, в чем заключается ваша проблема, поскольку ошибка во многом связана с тем, что происходит в эволюция жизненного цикла среды выполнения, но, надеюсь, это будет полезно
Ответ на ваш комментарий
Если один из ваших потоков данных (LiveData) зависит от того, какой другой поток данных (другой LiveData) будет излучать, вы ищете операции LiveData.map
и LiveData.switchMap
.
Я предполагаю, что hourlyWeatherEntries
будет время от времени испускать значения.
В этом случае вы можно использовать LiveData.switchMap
.
Это означает, что каждый раз, когда источник LiveData
выдает значение, вы получите обратный вызов, и ожидается, что вы вернете новые данные в реальном времени с новым значением.
Вы можете организовать что-то вроде следующего:
val hourlyChartData = hourlyWeatherEntries.switchMap { hourlyWeather ->
liveData {
val task = viewModelScope.async(Dispatchers.IO) {
createChartData(hourlyWeather)
}
emit(task.await())
}
}
Преимущество этого подхода в том, что он полностью ленив. Это означает, что НЕТ ВЫЧИСЛЕНИЙ не будет ЕСЛИ data
не будет активно наблюдаться некоторыми lifecycleOwner
. Это просто означает, что никакие обратные вызовы не запускаются, если data
не наблюдается в Fragment
Дальнейшее объяснение map
и switchMap
Поскольку нам нужно выполнить некоторые асинхронные вычисления что мы не знаем, когда это будет сделано, мы не можем использовать map
. map
применяет линейное преобразование между LiveDatas. Проверьте это:
val liveDataOfNumbers = liveData { // Returns a LiveData<Int>
viewModelScope.async {
for(i in 0..10) {
emit(i)
delay(1000)
}
}
}
val liveDataOfDoubleNumbers = liveDataOfNumbers.map { number -> number * 2}
Это действительно полезно, когда вычисления линейны и просты. За капотом происходит то, что библиотека обрабатывает наблюдение и генерирует значения за вас с помощью MediatorLiveData
. Здесь происходит следующее: всякий раз, когда liveDataOfNumbers
генерирует значение и наблюдается liveDataOfDoubleNumbers
, применяется обратный вызов; поэтому liveDataOfDoubleNumbers
излучает: 0, 2, 4, 6…
Приведенный выше фрагмент эквивалентен следующему:
val liveDataOfNumbers = liveData { // Returns a LiveData<Int>
viewModelScope.async {
for(i in 0..10) {
emit(i)
delay(1000)
}
}
}
val liveDataOfDoubleNumbers = MediatorLiveData<Int>().apply {
addSource(liveDataOfNumbers) { newNumber ->
// Update MediatorLiveData value when liveDataOfNumbers creates a new number
value = newNumber * 2
}
}
Но просто использовать map
намного проще .
Fantasti c !!
Теперь перейдем к вашему варианту использования. Вычисления линейны, но мы хотим отложить эту работу до фоновой сопрограммы. Поэтому мы не можем точно сказать, когда что-то закончится.
Для этих случаев использования они создали оператор switchMap
. Что он делает, то же самое, что и map
, но все оборачивает в другой LiveData
. Промежуточные LiveData просто действуют как контейнер для ответа, который будет исходить от сопрограммы.
В итоге происходит следующее:
- Ваша сопрограмма публикуется в
intermediateLiveData
- switchMap делает что-то похожее на:
return MediatorLiveData().apply {
// intermediateLiveData is what your callback generates
addSource(intermediateLiveData) { newValue -> this.value = newValue }
} as LiveData
Подводя итог: 1. Сопрограмма передает значение intermediateLiveData
2. intermediateLiveData
передает значение hourlyChartData
3. hourlyChartData
передает значение пользовательскому интерфейсу
И все, не добавляя и не удаляя observeForever
Поскольку liveData {…}
- это конструктор, который помогает нам создавать асинхронные LiveDatas без необходимости создавать их экземпляры, мы можем использовать его, чтобы наш обратный вызов switchMap
был менее подробным.
Функция liveData
возвращает живые данные типа LiveData<T>
. Если ваш вызов репозитория уже возвращает LiveData, это действительно просто!
val someLiveData = originalLiveData.switchMap { newValue ->
someRepositoryCall(newValue).map { returnedRepoValue -> /*your transformation code here*/}
}