11

I'm using the Navigation Component version 2.1.0-rc01 and I navigate back and forth between 3 screens using

Navigation.findNavController(it).navigate(R.id.action_participants)

After going through the same screens a second time I can see the second fragment but I receive an exception. I enabled the log on the FragmentManager and it seems like there is a difference instance of the same fragment that is not attached, causing the error

Any ideas on why the Navigation Component is creating another instance of the fragment that is not being attached? Any workarounds to get the attached fragment instead?

    2019-08-15 16:59:30.895 30041-30041/com.app.debug D/FragmentManager:   mName=3-2131361912 mIndex=-1 mCommitted=false
2019-08-15 16:59:30.895 30041-30041/com.app.debug D/FragmentManager:   mEnterAnim=#7f01001e mExitAnim=#7f01001f
2019-08-15 16:59:30.896 30041-30041/com.app.debug D/FragmentManager:   mPopEnterAnim=#7f010020 mPopExitAnim=#7f010021
2019-08-15 16:59:30.896 30041-30041/com.app.debug D/FragmentManager:   Operations:
2019-08-15 16:59:30.896 30041-30041/com.app.debug D/FragmentManager:     Op #0: REPLACE StaffBookingDetailsFragment{82e8301 (97f79b28-d8c1-432a-9e1c-3a781dd42434) id=0x7f0a01c5}
2019-08-15 16:59:30.896 30041-30041/com.app.debug D/FragmentManager:   enterAnim=#7f01001e exitAnim=#7f01001f
2019-08-15 16:59:30.896 30041-30041/com.app.debug D/FragmentManager:   popEnterAnim=#7f010020 popExitAnim=#7f010021
2019-08-15 16:59:30.896 30041-30041/com.app.debug D/FragmentManager:     Op #1: SET_PRIMARY_NAV StaffBookingDetailsFragment{82e8301 (97f79b28-d8c1-432a-9e1c-3a781dd42434) id=0x7f0a01c5}
2019-08-15 16:59:30.897 30041-30041/com.app.debug D/FragmentManager:   enterAnim=#7f01001e exitAnim=#7f01001f
2019-08-15 16:59:30.897 30041-30041/com.app.debug D/FragmentManager:   popEnterAnim=#7f010020 popExitAnim=#7f010021
2019-08-15 16:59:31.935 30041-30041/com.app.debug D/FragmentManager:   mName=4-2131362286 mIndex=-1 mCommitted=false
2019-08-15 16:59:31.935 30041-30041/com.app.debug D/FragmentManager:   Operations:
2019-08-15 16:59:31.936 30041-30041/com.app.debug D/FragmentManager:     Op #0: REPLACE ParticipantsFragment{fdd9ef9 (b7317713-b150-44a2-8b1c-47a0f8c52781) id=0x7f0a01c5}
2019-08-15 16:59:31.936 30041-30041/com.app.debug D/FragmentManager:     Op #1: SET_PRIMARY_NAV ParticipantsFragment{fdd9ef9 (b7317713-b150-44a2-8b1c-47a0f8c52781) id=0x7f0a01c5}
2019-08-15 16:59:55.266 30041-30041/com.app.debug E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.app.debug, PID: 30041
    java.lang.IllegalStateException: Fragment ParticipantsFragment{b6e8bc7 (aa204a1e-5f3a-40c0-86f0-b5edab4b07eb)} not associated with a fragment manager.
        at androidx.fragment.app.Fragment.requireFragmentManager(Fragment.java:910)
        at com.app.bookings.participants.ParticipantsFragment.onParticipantActionClicked(ParticipantsFragment.kt:88)
        at com.app.databinding.ItemBindParticipantBindingImpl._internalCallbackOnClick(ItemBindParticipantBindingImpl.java:218)
        at com.app.generated.callback.OnClickListener.onClick(OnClickListener.java:11)
        at android.view.View.performClick(View.java:6669)
        at android.view.View.performClickInternal(View.java:6638)
        at android.view.View.access$3100(View.java:789)
        at android.view.View$PerformClick.run(View.java:26145)
        at android.os.Handler.handleCallback(Handler.java:873)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:193)
        at android.app.ActivityThread.main(ActivityThread.java:6863)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:537)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
Leonardo Deleon
  • 2,577
  • 5
  • 15
  • 22

7 Answers7

16

Make sure that the Fragment is garbage collected/destroyed. Fragment will not be garbage collected/destroyed if any lifecycle unaware registered listeners (listeners that do not support androidx.lifecycle.Lifecycle) are registered in onCreateView/onViewCreated etc methods. Make sure that you unregister such listeners in onDestroyView() of fragment.

Example : OnBackPressedDispatcher is not lifecycle aware. Therefore it expects you to unregister when that fragment is destroyed. If it is not unregistered then it keeps a reference and gets called when back is pressed in some other fragment also.

I was calling findNavController().navigateUp() inside


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

             val onBackPressedCallback = object : OnBackPressedCallback(true) {
                override fun handleOnBackPressed() {
                    //some logic that needs to be run before fragment is destroyed
                    findNavController().navigateUp()
                }
            }
            requireActivity().onBackPressedDispatcher.addCallback(
                  onBackPressedCallback
            )
        }

and if you look at documentation of findNavController()

Calling this on a Fragment that is not a [NavHostFragment] or within a [NavHostFragment] will result in an [IllegalStateException]

This is why I was getting

IllegalStateException Fragment not associated with a fragment manager

Solution :

Unregister Listeners in onDestroyView

override fun onDestroyView() {
    super.onDestroyView()
    //unregister listener here
    onBackPressedCallback.isEnabled = false
    onBackPressedCallback.remove()
}
Ramakrishna Joshi
  • 1,442
  • 17
  • 22
  • 1
    thank you for detailed explanation. this should be marked as correct answer. – Pietrek Oct 07 '20 at 07:30
  • 2
    [No more true in last version](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:activity/activity/src/main/java/androidx/activity/OnBackPressedDispatcher.java;l=101;drc=544343b7b8f1a7479b83f57163f67276193067ef) – i30mb1 Oct 14 '20 at 15:44
  • @i30mb1 The doc says `gives access to the Cancellable that specifically removes this callback from the dispatcher without relying on OnBackPressedCallback#remove()` but there is no mention of when the callback will be removed. – Ramakrishna Joshi Oct 14 '20 at 18:52
  • look below and you will see when `event == Lifecycle.Event.ON_DESTROY` – i30mb1 Oct 14 '20 at 19:02
8

If add the lifecycle Owner, you do not need to remove the callback in onDestroyDocumentation

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

             val onBackPressedCallback = object : OnBackPressedCallback(true) {
                override fun handleOnBackPressed() {
                    findNavController().navigateUp()
                }
            }
            // ADD LIFECYCLE OWNER
            requireActivity().onBackPressedDispatcher.addCallback(this,
                  onBackPressedCallback
            )
        }
sadat
  • 4,004
  • 2
  • 29
  • 49
0

You can use the XML property popUpToInclusive="true" of the navigation action to specify if old instances of the same destination should be popped. Also see the documentation

0

Navigation.findNavController(it).navigate(R.id.action_participants)

Instead of the above place

Navigation.findNavController(context).navigate(R.id.action_participants)

srinivasu
  • 17
  • 2
0

I found that you used viewbinding. This class was created in the adapter and cannot directly use findnavcontroller. You need to pass the current fragment to the adapter, and then pass the past fragment to the required class. If you need activity, the same is true. Pass the requireactivity to the adapter and then to the required class

0

@Ramakrishna Joshi is correct. In my case the issue was that I was using

val menuHost: MenuHost = requireActivity()    
menuHost.addMenuProvider(menuProvider)

so I had to remove it on onDestroyView like this

override fun onDestroyView() {
    val menuHost: MenuHost = requireActivity()
    menuHost.removeMenuProvider(menuProvider)
    super.onDestroyView()
}
hushed_voice
  • 3,161
  • 3
  • 34
  • 66
-1

Upon further investigation, I verified that this is just a side effect when the fragment is not disposed properly. It is solved now.

Leonardo Deleon
  • 2,577
  • 5
  • 15
  • 22