Search code examples
androidandroid-fragmentsrx-javaviewmodeldisposable

ViewModel and LiveData persist in destroyed fragment causing NPE on second access to fragment


I have a ViewModel that retrieves data from a single RxJava Observable using a Disposable.

internal class MyViewModel: ViewModel() {
   internal var disposable: Disposable? = null

   internal var myMutableLiveData= MutableLiveData<List<...>?>()

   internal fun getData(myParam: String) {
       disposable = (ApiServiceClient.createApiService().getDataFromAPI(
           myParam
    )).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(
        { response -> myMutableLiveData.postValue(response) },{ myMutableLiveData.postValue(null) }
    )
   }
}

In my fragment, I observe myMutableLiveData in onViewCreated to retrieve the data. In onDestroyView of the fragment, I release resources.

internal class MyFragment: Fragment() {

private var _binding: FragmentMyBinding? = null
private val binding get() = _binding!!
private val viewModel: MyViewModel by viewModels()

override fun onCreateView(inflater: LayoutInflater,container: ViewGroup?,savedInstanceState: Bundle?): View {
    _binding = FragmentMyBinding.inflate(inflater, container, false)
    return binding.root
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    viewModel.myMutableLiveData.observe((context as MyActivity)) { data ->
        if (data == null) // Error
        else {
    // Success, use binding
        }
    }
    viewModel.getData("text")
}

override fun onDestroyView() {
    super.onDestroyView()
    _binding = null
    viewModel.disposable.dispose()
}

}

The first time I choose the fragment MyFragment, everything works fine. However, when I switch to another fragment within the same activity (MyFragment pass through onDestroyView, onDestroy and onDetach) and then I return to MyFragment (onCreate, onCreateView, onViewCreated), it seems as if the ViewModel is still there, causing the observation to happen before the binding class is reconstructed, resulting in a NullPointerException.

Why this? What is the correct way to manage the lifecycle of Fragment-ViewModel-Disposable?


Solution

  • While there might be some niche use cases to use Activity as the owner for the observer - your case seems to not be one.

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        viewModel.myLiveData.observe(viewLifecycleOwner) { data ->
            if (data == null) {
                // Error
            } else {
                // Success, use binding
            }
        }
        viewModel.getData("text")
    }
    

    Also, it is not a good idea to expose mutable LiveData.

    private val _myMutableLiveData = MutableLiveData<List<...>?>()
    internal val myLiveData = _myMutableLiveData.asLiveData()
    

    With a note that LiveData might return null if it is not being observed/hasn't started yet.