Skip to content

Instantly share code, notes, and snippets.

@william-reed
Created May 4, 2020 13:00
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save william-reed/a506aae7667b22f59646187db85303d6 to your computer and use it in GitHub Desktop.
Save william-reed/a506aae7667b22f59646187db85303d6 to your computer and use it in GitHub Desktop.
prevent “… is unknown to this NavController” caused by double navigation
import android.os.Bundle
import androidx.annotation.IdRes
import androidx.navigation.NavController
import androidx.navigation.NavDirections
import com.busright.watchdog.BuildConfig
import timber.log.Timber
import java.lang.IllegalArgumentException
/**
* This file represents attempts to make my errors in navigation safer.
*
* Several times I have seen crashed related to "`action_*_to_*` is not known to this NavController"
* I have been able to fix one of these which was the result of a listener getting added twice,
* and as a result the `.navigate` method was called twice and resulted in this error. I added the
* following to allow the app to keep running in those events and to log the important details of
* what just happened. This will allow me to eventually fix the issue and not worry about a crash.
*
* The main ideas here are that each time a navigation event is successful, the timestamp and action
* is stored. The next time a navigation event happens, if it fails, I check if it was the same
* action id as the last one that succeeded and if it was within some short time period (see below).
* If it was I can reasonably say that there is a bug and just to ignore the double call and to log
* it so the bug can hopefully be fixed, if it is not, raise the error again since something really
* has gone wrong.
*/
/** the last time a navigation was attempted. pair of action id / direction to the timestamp */
private var lastNav: Pair<Int, Long>? = null
/** maximum time to consider a navigation action to be the same as the previous */
private const val MAX_TIME_TO_CATCH_MS = 10L
/** The same as [NavController.navigate] except with a catch */
fun NavController.navigateSafe(directions: NavDirections) {
try {
// navigate and update lastNav
navigate(directions)
lastNav = directions.actionId to System.currentTimeMillis()
} catch (e: IllegalArgumentException) {
errorOnNav(directions.actionId, e)
}
}
/** The same as [NavController.navigate] except with a catch */
fun NavController.navigateSafe(@IdRes directions: Int, args: Bundle? = null) {
try {
navigate(directions, args)
lastNav = directions to System.currentTimeMillis()
} catch (e: IllegalArgumentException) {
errorOnNav(directions, e)
}
}
/** handle the nav related errors to compare it to lastNav */
private fun errorOnNav(@IdRes action: Int, e: IllegalArgumentException) {
// if lastNav exists, maybe we can catch it
lastNav?.let { lastNav ->
val lastAction = lastNav.first
val lastTime = lastNav.second
val timeDiff = System.currentTimeMillis() - lastTime
// if the last time we navigated it went successful, we must be double navigating
if (lastAction == action && timeDiff < MAX_TIME_TO_CATCH_MS) {
Timber.e(e, "$action was navigated to multiple times - there might be a listener that got added twice. The last attempt was $timeDiff ms ago.")
// if we are in debug, throw the error anyway
if (BuildConfig.DEBUG) throw e
else return
} else {
// if it wasn't recent, or the action doesn't match, yeet it
throw e
}
}
// if we don't have a last nav yeet it again
throw e
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment