I have a callback method in my fragment which gets called from it's ViewModel. It initializes the variable in the OnCreateView()
method of the fragment, but when the ViewModel calls it to use it, its null.
I am thinking that it has something to do with maybe the VM getting recreated somehow? I just can't seem to figure it out.
I am following this answer's of how the VM drives the UI. They provide Google's sample of a callback interface being created (TasksNavigator.java), Overriding the method in the View (TasksActivity.java), and then calling that method from the VM (TasksViewModel.java) but it doesn't seem to work for me.
Fragment
class SearchMovieFragment : Fragment(), SearchNavigator {
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? {
searchMovieFragmentViewModel = ViewModelProvider(this).get(SearchMovieFragmentViewModel::class.java)
binding = DataBindingUtil.inflate(inflater, R.layout.search_movie_fragment, container, false)
binding.viewmodel = searchMovieFragmentViewModel
searchMovieFragmentViewModel.setNavigator(this)
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()
binding.searchMovieFragmentRecyclerView.adapter = adapter
searchMovieFragmentViewModel.movieList.observe(viewLifecycleOwner, Observer {
adapter.submitList(it)
})
}
override fun openDetails() {
Log.d("TEST", "opening details")
}
}
ViewModel
class SearchMovieFragmentViewModel : ViewModel(), MovieSearchItemViewModel {
private lateinit var searchNavigator: SearchNavigator
val editTextContent = MutableLiveData<String>()
var movieList = Repository.getMovieList("batman")
fun setNavigator(_searchNavigator: SearchNavigator) {
this.searchNavigator = _searchNavigator
if (searchNavigator != null) {
Log.d("TEST", "its not null $searchNavigator") // Here it is not null
}
}
private fun getMovieDetail(movieId: String) {
val movie = Repository.getMovieDetail(movieId)
Log.d("TEST", "checking ${this.searchNavigator}") // Here is where I call it but it is null
// searchNavigator.openDetails()
}
private fun getMovieList(movieSearch: String): MutableLiveData<List<Movie>> = Repository.getMovieList(movieSearch)
override fun displayMovieDetailsButton(movieId: String) {
Log.d("TEST", "button clicked $movieId")
getMovieDetail(movieId)
}
}
CallBack Interface
interface SearchNavigator {
fun openDetails()
}
Initiate ViewModel in below method of fragment
override onActivityCreated(@Nullable final Bundle savedInstanceState){
searchMovieFragmentViewModel = ViewModelProvider(this).get(SearchMovieFragmentViewModel::class.java)
}
I will recommend use live data to create connection between ViewModel and and Fragment it will be safer and correct approach.
Trigger openDetails based on the trigger's from your live data.It's forbidden to send your view(context
) instance to ViewModel even if you wrap it as there is high probability of memory leaks.
But if you still want to follow this approach then you should Register and unregister fragment instance in your ViewModel (keep a list of SearchNavigator) it onStop()
and onStart()
.
and loop through them to call openDetails