'Navigation Error: action/destination cannot be found from the current destination

I am using Navigation component to navigate between two Fragments. The landing fragment has a recycler view and the detail fragment has a view pager. I am using a call back listener to trigger navigation action from the recycler view adapter.

The action to be trigger is a zoom event, with the library ZoomHelper ZoomHelper

When the event happens the app crashes with the error above.

However it works well with onclick event listener.

View Holder

class CampaignListViewHolder<T : ViewBinding>(private val binding: T) : RecyclerView.ViewHolder(binding.root) {

    var campaignDetails: CampaignDetails? = null

    @SuppressLint("ClickableViewAccessibility")
    fun bindTo(campaignDetails: CampaignDetails?, position: Int, listener: ItemZoomListener) {
        this.campaignDetails = campaignDetails

        binding as CampaignItemLayoutBinding
        binding.campaignNameTv.text = campaignDetails?.name
        binding.campaignImageIv.load(campaignDetails?.image?.url) {
            crossfade(true)
            placeholder(R.drawable.westwing_placeholder)
        }
        ViewCompat.setTransitionName(binding.campaignImageIv, campaignDetails?.name)
        binding.root.setOnClickListener {
            if (campaignDetails != null) {
                listener.navigate(position)
            }
        }

        ZoomHelper.addZoomableView(binding.campaignImageIv)
        
        ZoomHelper.getInstance().addOnZoomScaleChangedListener(object :
                ZoomHelper.OnZoomScaleChangedListener {
                override fun onZoomScaleChanged(
                    zoomHelper: ZoomHelper,
                    zoomableView: View,
                    scale: Float,
                    event: MotionEvent?
                ) {
                    // called when zoom scale changed
                  
                    if (campaignDetails != null && scale > 1.4) {
                        listener.navigate(position)
                    }
                }
            })
    }
}

Landing Fragment

class LandingFragment : Fragment(R.layout.fragment_landing), ItemZoomListener, FragmentUiStateListener {
    private val TAG by lazy { getName() }
    private val binding by viewBinding(FragmentLandingBinding::bind)
    private val campaignListViewModel: CampaignListViewModel by activityViewModels()
    lateinit var campaignViewAdapter: CampaignListViewAdapter
    lateinit var activityUiState: ActivityUiStateListener
    lateinit var fragmentUiUpdate: FragmentUiStateListener

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        campaignViewAdapter = CampaignListViewAdapter(this)
        fragmentUiUpdate = this
    }

    override fun onResume() {
        super.onResume()
        setupView()
        setUpData()
    }

    private fun setUpData() {
        setUpUiState(campaignListViewModel.campaignUiState, fragmentUiUpdate)
    }

    private fun setupView() {
        val orientation = checkOrientation()
        if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
            binding.campaignLandscapeRcv?.apply {
                layoutManager = GridLayoutManager(requireContext(), 2)
                adapter = campaignViewAdapter
                addItemDecoration(ItemSpaceDecoration(8, 2))
            }
        } else {
            binding.campaignListRcv?.apply {
                layoutManager = LinearLayoutManager(requireContext())
                adapter = campaignViewAdapter
                addItemDecoration(ItemSpaceDecoration(8, 1))
            }
        }
    }

    override fun navigate(position: Int) {
        val direction = LandingFragmentDirections.actionLandingFragmentToCampaignDetailsFragment(position)
        goto(direction)
    }



I understand that one of the reason for the error is probably the zoom event calling the navigation controller multiple times but I can not figure how debug that and what could be a way around this.



Solution 1:[1]

As you guessed, the issue is most likely caused by the navController being fired multiple times. You can handle this by "navigating safely". Here's a sample implementation below:

fun NavController.safelyNavigate(@IdRes resId: Int, args: Bundle? = null) = 
    try { navigate(resId, args) }
    catch (e: Exception) { Timber.e(e) }
}

You can then make your navigation call like this:

findNavController().safelyNavigate(your_id)

This way, any extra call to NavController.navigate() gets absorbed in the try and catch.

Crash prevented :D

Solution 2:[2]

If someone had the same issues due to multiple clicks on the screen. It can be resolved by checking the current destination first before navigating

For example

Fragments A, B, and C

navigating from A to B while clicking on a button in fragment A that navigates to C might lead to crashes in some cases

for that you should check the current destination first as follows:

if(findNavController().currentDestination?.id==R.id.AFragment)
   findNavController().navigate(
   AFragmentDirections.actionAFragmentToCFragment()
 )

Solution 3:[3]

If it is being called twice, then you can remove the 'ZoomHelper.OnZoomScaleChangedListener' before the navigation occurs.

I haven't tested the code below, but you can explore the library's code here https://github.com/Aghajari/ZoomHelper/blob/master/ZoomHelper/src/main/java/com/aghajari/zoomhelper/ZoomHelper.kt. You will find a method called 'removeOnZoomScaleChangedListener' which from what I understand, will remove a listener of type 'ZoomHelper.OnZoomScaleChangedListener'.

Ex.

val onZoomScaleChangedListener = object :
                            ZoomHelper.OnZoomScaleChangedListener {
                            override fun onZoomScaleChanged(
                                zoomHelper: ZoomHelper,
                                zoomableView: View,
                                scale: Float,
                                event: MotionEvent?
                            ) {
                                // called when zoom scale changed
                                if (campaignDetails != null && scale > 1.4) {
              ZoomHelper.getInstance().removeOnZoomScaleChangedListener(onZoomScaleChangedListener) // Remove this event listener to avoid multiple triggers as you only need 1.
                                    listener.navigate(position)
                                }
                            }
                        }
        
      ZoomHelper.getInstance().addOnZoomScaleChangedListener(onZoomScaleChangedListener) // You can add the listener above like this.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Taslim Oseni
Solution 2
Solution 3 Rigo Regalado