I'm having troubles setting up MVVM with my project, everything seems to be running smoothly except I've noticed the Observers for my LiveData objects are being called multiple times. Sometimes 4-5 times for the same LiveData object updating once.
There are multiple fragments hosted on the main activity, all linked up to the same ViewModel that connects to my repository which then uses retrofit to make an API request to my server.
I then have individual observers in each fragment, observing the same LiveData object from the ViewModel for changes, when using a singular fragment I didn't have issues of multiple observations for the same change but upon branching out the project to have a multitude of fragments I've noticed that the observer is called a lot.
I've changed the observers to all use the viewLifecycleOwner to no avail.
Here's an example of a fragment, each one is essentially identical in the way they observe the LiveData except they update different parts of the UI.
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProviders.of(this, viewModelFactory)
.get(WeatherViewModel::class.java)
bindUI()
}
private fun bindUI() = launch {
val currentWeatherResponse = viewModel.weatherResponse.await()
currentWeatherResponse.observe(viewLifecycleOwner, Observer {
if(it == null) return@Observer
Log.i("CurrentWeatherFragment", "Observed")
val currentWeather = it.currentWeather.first()
updateTemperature(currentWeather.temperature, currentWeather.tempMin, currentWeather.tempMax)
updateWind(currentWeather.windSpeed, currentWeather.windDirName)
updateHumidity(currentWeather.humidity)
})
}
This is the ViewModel that each fragment hooks up to:
class WeatherViewModel(
private val forecastRepository: IForecastRepository
) : ViewModel() {
val weatherResponse by lazyDeferred {
forecastRepository.getWeatherResponse()
}
}
The weather response method within my repository:
override suspend fun getWeatherResponse(): LiveData<out DetailedPrediction> {
return withContext(Dispatchers.IO) {
initWeatherData()
println("Get Weather response method")
println(currentWeatherDao.getWeatherResponse().value)
return@withContext currentWeatherDao.getWeatherResponse()
}
}
And the weather DAO which receives data posts from the saveWeatherResponse method:
private val weatherResponse = MutableLiveData<DetailedPrediction>()
private val radarImages = MutableLiveData<RadarImageList>()
//TODO Return weather response from shared preferences
override fun getWeatherResponse(): LiveData<DetailedPrediction> {
return weatherResponse
}
I'm expecting to be able to update all my UI elements from this singular LiveData event, which is actually working but the observers are triggering far too many times and I can't for the life of me figure out why.
Since we are using switchmap in viewmodel and the livedata was set again the observer in fragment A was getting triggered twice. To fix this all i had to do was check if livedata had the same value that is being set now to avoid making network calls and triggering livedata in fragment A twice.
In theory, we can observe livedata with multiple observers, just like the good old pub-sub pattern, right? Well, let's test this out. Let's create a super simple livedata. Then observe it 10 times, and print some log when the data changes.
LiveData is an observable data holder class. Unlike a regular observable, LiveData is lifecycle-aware, meaning it respects the lifecycle of other app components, such as activities, fragments, or services. This awareness ensures LiveData only updates app component observers that are in an active lifecycle state.
The observe() method takes a LifecycleOwner object. This subscribes the Observer object to the LiveData object so that the observer is notified of changes. You usually attach the Observer object in a UI controller, such as an activity or fragment.
I can't believe it's taken me this long to figure out a simple mistake but if anyone ever runs into this problem (I doubt it) i was adding a fragment on every observation rather than replacing the fragment, this meant a brand new observer was created for each fragment.
Here's the old code:
fun addDailyWeatherFragment() {
val fragmentTransaction: FragmentTransaction = fragmentManager!!.beginTransaction()
val dailyWeatherFragment = DailyWeatherFragment()
fragmentTransaction.add(R.id.fragmentContainer, dailyWeatherFragment)
fragmentTransaction.commit()
}
And the fixed code:
fun addDailyWeatherFragment() {
val fragmentTransaction: FragmentTransaction = fragmentManager!!.beginTransaction()
val dailyWeatherFragment = DailyWeatherFragment()
fragmentTransaction.replace(R.id.fragmentContainer, dailyWeatherFragment)
fragmentTransaction.commit()
}
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