I have this livedata "events" that should be updated if the method loadEvents() gets fired. The thing is, I need to pass many different parameters to that method. If I call this method and then observe the data, the data is null. How can I do this?
Viewmodel:
private val _events = MutableLiveData<List<BookEvent>>()
val events: LiveData<List<BookEvent>> get() = _events
fun loadEvents( dateTo: String,
dateFrom: String?,
dateTime: String?,
pageIndex: Int,
types: String){
viewModelScope.launch {
val response = eventsBooksRepository.getEvents(
pageSize = 100,
pageIndex = pageIndex 1,
types = types,
dateTime= dateTime,
dateFrom = dateFrom)
if(response.value?.isSuccessful() == true){
_events.value = response.value?.data
} else {
_events.value = null
}
}
}
In my fragment, getEvents() works if I don't call retrieveEvents() first but I need to call retrieveEvents() with different parameters, depending on the different outcomes. If I call retrieveEvents() then I get "java.lang.NullPointerException: it must not be null" when observing the events.
Fragment:
private fun retrieveEvents(){
lifecycleScope.launch {
myViewModel.loadEvents(
dateTime= if (eventsByDate) dateTime else "",
dateFrom = if (previousEvents) dayDateFrom else "",
pageIndex = 1,
types = myViewModel.filtersApplied
)
}
}
private fun getEvents(){
myViewModel.events.observe(viewLifecycleOwner){
val events = it.map { event ->
val descriptionPair = getEventIcon(event)
bookEventModel.bookEventItem(
bookEventModel(
event.eventId,
event.eventType,
event.eventDate,
event.bookState
)
)
}
binding.eventsRecycler.adapter = eventsAdapter
eventsAdapter.addAll(events)
}
}
How can I call retrieveEvents() whenever I want to update the livedata without getting a null object?
CodePudding user response:
This is the LiveData
you're observing:
val events: LiveData<List<BookEvent>>
The data is holds is a non-null type, List<BookEvent>
. So when you call observe
on it and pass your lambda function, that function's inferred type is (List<BookEvent>) -> Unit
- i.e. it takes a non-null List<BookEvent>
parameter, because that's the type that events
emits.
So you're calling it.map
and you're not getting warned to null-check it
, because that parameter is (implicitly) declared as non-null. But it
is getting passed a null somehow, and that's why you're getting the crash.
Probably because of this:
if(response.value?.isSuccessful() == true){
_events.value = response.value?.data
} else {
_events.value = null
}
You're explicitly setting events
's value to null even though it's meant to only hold List<BookEvent>
s. You're able to do this (even though you shouldn't) because LiveData
uses null as a "no data" value when it's first created, if you don't provide an initial value. That initial placeholder null won't be emitted and observed - like you said, calling getEvents()
works fine, observing the LiveData
with no value doesn't trigger the observer and crash it with a null value.
It's only when you call retrieveEvents
and explicitly set a null value on events
that it emits that as a value, and that hits your observer function and causes all kinds of trouble. You're doing that intentionally as a fallback for when your fetch fails, which is a problem in itself - none of your observers are expecting a null value, they can't handle it. So either don't make that your fallback value (use emptyList()
, or maybe you don't want to push an update at all if it fails?) or make your LiveData
nullable, i.e. LiveData<List<BookEvent>?>
By the way, here's what happens for me if I try and set a non-nullable LiveData
to null:
Aren't you getting that warning? It won't stop you from building the app and running it, until you hit that null and it crashes, but it should at least be pointing out the problem to you. Make sure you're using an up-to-date version of the LiveData
lifecycle library in your gradle dependencies
CodePudding user response:
_events it's initially null. So in your fragment check null condition
You call view model methods directly until it's suspend function Like this
myViewModel.loadEvents()
Another suggestion use dispatchers to the scope and add errorHandler else try catch block.
Follow like this
viewModelScope.launch(Dispatchers.IO CoroutineExceptionHandler { _, exception ->
//Handle exceptions here
Log.e(TAG,exception.message)
}){
//Set method for the main thread. Use Post Value.
_events.postValue(response.value?.data)
//OR
viewModelScope.launch(Dispatchers.Main){
_events.value(response.value?.data)
}
}