I am trying to implement a SwipeToRefreshLayout in a weather app I'm building. When the user swipes to refresh, the data in the ViewModel should be updated, and then the view should be updated accordingly.
Here is a snippet of my CurrentWeatherFragment:
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProviders.of(this, viewModelFactory)
.get(WeatherResponseViewModel::class.java)
pullToRefresh.setOnRefreshListener(this)
bindUI()
}
override fun onRefresh() {
viewModel = ViewModelProviders.of(this, viewModelFactory)
.get(WeatherResponseViewModel::class.java)
bindUI()
pullToRefresh.isRefreshing = false
}
private fun bindUI() = launch {
val currentWeather = viewModel.weather.await()
currentWeather.observe(this@CurrentWeatherFragment, Observer {
if (it == null) return@Observer
loading_icon.visibility = View.GONE
updateLocation("Raleigh")
updateDateToToday()
updateTemperatures(it.currently.temperature.roundToInt(),
it.currently.apparentTemperature.roundToInt(),
it.daily.data[0].temperatureMin.roundToInt(),
it.daily.data[0].temperatureMax.roundToInt())
updateDescription(it.currently.summary)
updateEnvironmentals((it.currently.humidity * 100).roundToInt(), it.currently.windSpeed.roundToInt())
updateWeatherIcon(it.currently.icon)
updateTempChart(it)
updatePrecipChart(it)
})
}
my ViewModel:
class WeatherResponseViewModel (
private val forecastRepository: ForecastRepository,
unitProvider: UnitProvider
) : ViewModel() {
private val unitSystem = unitProvider.getUnitSystem()
val isMetric: Boolean
get() = unitSystem == UnitSystem.METRIC
val weather by lazyDeferred {
forecastRepository.getWeather()
}
}
and my lazyDeferred implementation:
fun <T> lazyDeferred(block: suspend CoroutineScope.() -> T): Lazy<Deferred<T>> {
return lazy {
GlobalScope.async {
block.invoke(this)
}
}
}
Currently, with this setup, the forecastRepository.getWeather() function in the ViewModel is called when the fragment is loaded either on app launch or when switching to it, but on the swipe to refresh, it isn't being called. How can I get the weather variable in the ViewModel to update so that the view can observe the change?
First, There are some wrong implementations about Coroutine and ViewModel in your code.
Do not re-instantiate ViewModel again in onRefresh
.
Instead, just make and call refresh method in your viewmodel when SwipeRefreshLayout is pulled.
Do not use GlobalScope in your Application if you can.
It can cause Work Leak and doesn't follow Structured concurrency rule.
Instead, use coroutineScope{}
block.
Do not use LifecycleOwner
of Fragment
for observing LiveData
in fragment. It can generate duplicated observer for LiveData
.
Instead, use viewLifecycleOwner
instance of fragment.
This is becuase when the fragment is restored in back stack, the views of fragment is re-created, but fragment.
If you match your observer lifecycle with views in fragment, Ovserver
s won't remain after view is destoryed.
Way to Refresh
Don't use lazy block. It seems useless like @Stanislav Bondar
's answer.
From Kotlin Delegated Properties:
lazy properties: the value gets computed only upon first access;
Don't use lazy
for mutable data
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With