Search code examples
androidkotlinmvvmandroid-livedataandroid-viewmodel

LiveData Observer does not update UI after data fetched from server in ViewModel


I am trying to fetch data from the server using retrofit get request, in the ViewModel, I initiate the request, OnResponse method of the request shows the data successfully being retrieved from the server but the observer in the fragment doesn't get updated. I am sharing the code below.

RETROFIT API CALL

fun getGenres(application: Context,callback:(List<Genre>)->Unit){
        val baseURL = "https://listen-api.listennotes.com/"
        var genreList: MutableLiveData<List<Genre>> =MutableLiveData()
        val logging = HttpLoggingInterceptor()
//      set your desired log level
        logging.level = HttpLoggingInterceptor.Level.BODY

        val httpClient = OkHttpClient.Builder()
//      add your other interceptors …
//      add logging as last interceptor
        httpClient.addInterceptor(logging)

        val gson = GsonBuilder()
            .setLenient()
            .create()

        val retrofit = Retrofit.Builder()
            .baseUrl(baseURL)
            .addConverterFactory(GsonConverterFactory.create(gson))
            .client(httpClient.build())
            .build()

        val api: ApiFunctions = retrofit.create(ApiFunctions::class.java)



        val call: Call<PodcastGetCategoryResponse> = api.getGenres()

        call.enqueue(object : Callback<PodcastGetCategoryResponse> {
            override fun onFailure(call: Call<PodcastGetCategoryResponse>, t: Throwable) {

                Toast.makeText(application, "failure" + t.stackTrace, Toast.LENGTH_SHORT).show()

            }

            override fun onResponse(
                call: Call<PodcastGetCategoryResponse>,
                response: Response<PodcastGetCategoryResponse>
            ) {


                response.body().apply {
                    callback(this?.genres!!)                    }
                Toast.makeText(application, "success: ${response.body()?.genres?.size}", Toast.LENGTH_SHORT).show()

            }

        })

        return genreList;
    }

This runs successfully and retrieves a list of Genres from the server.

VIEWMODEL CLASS

class CategoryViewModel(application: Application) : AndroidViewModel(application) {
private val rootJob = SupervisorJob()

private val coroutineContext: CoroutineContext
    get() = Dispatchers.Main + rootJob

 var listOfCategory: MutableLiveData<List<Genre>> = MutableLiveData()

init {
    getistOfCategory()
}

// this method is responsible for retrieving data from the result of the API call method using a coroutine.

 fun getistOfCategory() {
    CoroutineScope(coroutineContext).launch() {
        ApiFunctions.getGenres(getApplication<Application>(), { l -> listOfCategory.value = l } )
         listOfCategory.postValue(listOfCategory.value)
    }
  }

I assume that issue lies in this class but I can't figure it out.

**FRAGMENT Class **

class MainFragment : Fragment() {
private lateinit var viewModel: CategoryViewModel
private lateinit var binding: FragmentMainBinding

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    viewModel = ViewModelProviders.of(this).get(CategoryViewModel::class.java)
    viewModel.getistOfCategory()

}

override fun onCreateView(
    inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {

    binding =DataBindingUtil.inflate(inflater, R.layout.fragment_main, container, false)
    var myView: View = binding.root




    viewModel.listOfCategory.observe(this,
        Observer { list ->
            Toast.makeText(
                context,
                "Data Changed" + list?.size,
                Toast.LENGTH_LONG
            ).show()
        })

    return myView
}

What I want is that when new values are retrieved from the server it should be saved in LiveData in ViewModel class and the fragment should successfully observe them.


Solution

  • according to your current implementation the value of listOfCategory keeps changing with each update. this means when your list is updated the observers are not being notified cause they're observing another LiveData object (which was assigned to your reference before the update occurred) You need to make sure that you instantiate the liveData object only once and update its value whenever you get an update.

    Your code should look something like this

    fun getGenres(application: Context, callback:(List<Genre>)->Unit){
    ...
         response.body().apply {
                callback(this?.genres!!)
         }
    ...
    }
    
    
    
    fun getistOfCategory(): {
      CoroutineScope(coroutineContext).launch() {
          ApiFunctions.getGenres(getApplication<Application>(), l -> listOfCategory.value = l)
        }
    
    }