Search code examples
androidkotlinandroid-architecture-componentsandroid-navigation-editor

Trouble with NavController inside OnBackPressedCallback


I am getting a does not have a NavController set error inside a OnBackPressedCallback. Here is the code

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

    fragmentView = inflater.inflate(R.layout.patient_info_fragment, container, false)

    if(Utils.connectedToInternet()){
        fragmentView.pastScreeningsButton.visibility = View.GONE
    }

    requireActivity().onBackPressedDispatcher.addCallback(object : OnBackPressedCallback(true) {
        override fun handleOnBackPressed() {
                Navigation.findNavController(fragmentView).navigate(R.id.action_patientInfoFragment_to_patientsList)
        }
    })
    setHasOptionsMenu(true)


    return fragmentView
}

I am only getting this error when I come back into the fragment where this is implemented.

I have android:name="androidx.navigation.fragment.NavHostFragment" in the home fragment.

To clarify, I am using Navigation controller to do all my Navigation around my app and it works just fine. I only get this error inside this OnBackPressedCallback and only when the user navigates back into the fragment where this is implemented.

Let me know if you need to see any more of my code.


Solution

  • You might run into an issue with leaks of old instances of your fragment. Also it's not a good practice to store the created view within another variable like fragmentView. All in all your onCreateView implementation is doing too many things unrelated to its purpose.

    I'd suggest to split up the functionality into relevant life-cycle methods and use the fragment.view directly within your callback. To not run into an issue with unattached views, you then bind and unbind the callback with the life-cycle.

    class PatientInfoFragment: Fragment() {
    
        private val callback = object : OnBackPressedCallback(true) {
            override fun handleOnBackPressed() {
                Navigation.findNavController(view).navigate(R.id.action_patientInfoFragment_to_patientsList)
            }
        }
    
        override fun onCreate(
            savedInstanceState: Bundle?
        ) {
            super.onCreate(savedInstanceState)
            setHasOptionsMenu(true)
        }
    
        override fun onCreateView(
            inflater: LayoutInflater,
            container: ViewGroup?,
            savedInstanceState: Bundle?
        ): View = inflater.inflate(R.layout.patient_info_fragment, container, false)
    
        override fun onViewCreated(
            view: View,
            savedInstanceState: Bundle?
        ) {
            if(Utils.connectedToInternet()){
                view.pastScreeningsButton.visibility = View.GONE
            }
        }
    
        override fun onStart() {
            super.onStart()
            requireActivity().onBackPressedDispatcher.addCallback(callback)
        }
    
        override fun onStop() {
            callback.remove()
            super.onStop()
        }
    }
    

    The callback life-cycle binding can be bound with the fragment as lifecycle owner calling the other addCallback(LifecycleOwner, OnBackPressedCallback).

    Additionally you could have a look into Android KTX and Kotlin Android Extensions to simplify your implementation.