Android: LiveData postValue() getting null

1.4k views Asked by At

I am trying to transfer a value from one LiveData (Repository.getMovieList(editTextContent.value.toString()).value) to another LiveData (this.movieList.postValue) using postValue().

I am observing the movieList and want to change it's value from the Repo depending on different buttons that were clicked but I when it runs, it only gets the null value and doesn't wait till the Repo's LiveData gets their value.

Fragment xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <data>
        <variable
            name="viewmodel"
            type="com.example.movieapp.ui.search.SearchMovieFragmentViewModel" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".ui.search.SearchMovieFragment">

        <EditText
            android:id="@+id/search_movie_edit_text"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@={viewmodel.editTextContent}"
            android:inputType="text"
            android:hint="Movie Name" />

        <Button
            android:id="@+id/search_fragment_search_btn"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Search"
            android:onClick="@{() -> viewmodel.getMovieSearchList()}"/>

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/search_movie_fragment_recycler_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

    </LinearLayout>
</layout>

SearchMovieFragment

class SearchMovieFragment : Fragment(), MovieSearchItemViewModel {

    companion object {
        fun newInstance() = SearchMovieFragment()
    }

    private lateinit var searchMovieFragmentViewModel: SearchMovieFragmentViewModel
    private lateinit var binding: SearchMovieFragmentBinding
    private lateinit var movieRecyclerView: RecyclerView

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

        binding = DataBindingUtil.inflate(inflater, R.layout.search_movie_fragment, container, false)
        searchMovieFragmentViewModel = ViewModelProvider(this).get(SearchMovieFragmentViewModel::class.java)
        binding.lifecycleOwner = this
        binding.viewmodel = searchMovieFragmentViewModel

        setUpRecyclerView(container!!.context)
        return binding.root
    }

    private fun setUpRecyclerView(context: Context) {
        movieRecyclerView = binding.searchMovieFragmentRecyclerView.apply {
            this.layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
        }
        val adapter = MovieListAdapter()
        adapter.setCallback(this)
        binding.searchMovieFragmentRecyclerView.adapter = adapter

        searchMovieFragmentViewModel.getMovieListLiveData().observe(viewLifecycleOwner, Observer {movieList ->
            adapter.submitList(movieList)
        })
    }

}

SearchMovieViewModel

class SearchMovieFragmentViewModel : ViewModel() {

    val editTextContent = MutableLiveData<String>()
    var movieList: MutableLiveData<List<Movie>> =  MutableLiveData()

    fun getMovieSearchList(){
        this.movieList.postValue(Repository.getMovieList(editTextContent.value.toString()).value)
    }

    fun getTrendingMovies() {
        movieList.postValue(Repository.getTrendingMovies().value)
    }

    fun getMovieDetail(movieId: String): MutableLiveData<Movie> {
        return Repository.getMovieDetail(movieId)
    }

    fun getMovieListLiveData() : LiveData<List<Movie>> {
        return movieList
    } 

    private fun getMovieList(movieSearch: String): MutableLiveData<List<Movie>> = Repository.getMovieList(movieSearch)

}
1

There are 1 answers

3
Hamza Sharaf On BEST ANSWER

I think you are implementing it the wrong way, Instead, using the MediatorLiveData will be a good and practical solution as it allows you to observe multiple LiveData objects and select between them based on your preferences (a specific action for example).

This is an example of how to implement it in your case

    val editTextContent = MutableLiveData<String>()

    val finalList = MediatorLiveData<List<Movie>>()

    // Here.. Define all of your LiveData objects
    private val movieList = repository.getMovieList(editTextContent.value.toString())
    private val trendingMovies = repository.getTrendingMovies()
    private val movieDetail = repository.getMovieDetail()

    fun setSelection(selection: String) {
        finalList.addSource(movieList) { result ->
            if (selection == "movieList") {
                result?.let { finalList.value = it }
            }
        }
        finalList.addSource(trendingMovies) { result ->
            if (selection == "trendingMovies") {
                result?.let { finalList.value = it }
            }
        }
        finalList.addSource(movieDetail) { result ->
            if (selection == "movieDetail") {
                result?.let { finalList.value = it }
            }
        }
    }

So what you have to do is to only observe the MediatorLiveData and then call the setSelection function and send the correspondent selection action to it as a parameter and it will switch the observation to another LiveData