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