'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 |