Home > OS >  How to update livedata through a method with multiple parameters
How to update livedata through a method with multiple parameters

Time:07-15

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:

Android Studio showing an error when setting 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)
       }
    }
  • Related