Example graph of an application’s navigation generated by Google’s NavigationComponent
Last year Google released the NavigationComponent which gives a straightforward and comprehensive overview of how your application’s navigation works. Thanks to its clear visual graph, grasping a better understanding of it is easier than ever before. It is an undeniably great tool to have, no questions about it, however, in its current state, there are a couple of pitfalls you might bump into.
One of them is the
IllegalArgumentException Navigation Destination Foo is unknown to this NavController that most of us have already encountered. This is the error you come across when you use a wrong action to the NavController, and there’s just something about this one that gives me some downright mixed feelings. On the one hand, it’s great, that it notifies you about some potentially nasty faults and mistakes (hopefully) before you release the app. But on the other hand, it’s a bummer, that this error can be caused by as little as clicking (accidentally, or due to sheer inattention) two views that set off two navigation events on the same screen. One of those click events triggers the navigation, changing the CurrentDestination, meaning the second event will cause this exception making your app crash.
As a first option, you could just wrap all your navigation calls into try-catch and ignore the exception, although this can seriously clutter your code and hide actual mistakes (plus throwing Exception is a rather slow operation).
At this point another, perhaps even better alternative would be to check if the
NavController.currentDestination.id is matching your current screen’s destinationId. This way even if a wrong destination is provided you’ll receive the exception, however clicking two views will no longer crash your app. You’d still need to add an if check for every navigation call, but you can move the check into a method of a base class that has an abstract protected field with the expected currentDestinationId, and that will do the checking for you. Yes, you read it right, pursuing this option means creating another base class so maybe just forget about this one…
Instead, you could move the check into a static function that receives the action and the expected currentDestinationId, no base class required. Aaaand, that’s it, problem solved!
Well, yes, but actually I’ve been using this for a while now, and maybe I’m just too sloppy, but a lot of times I’ve made a copy-pasting error or simply forgot to add the check. After a few occasions, I asked myself, “Wouldn’t it be convenient if I could have a
findSafeNavController() and use it everywhere I want to
Yes, it would be convenient. But can it be done?
Of course, besides, it is already implemented, here is the source code if you are interested.
The gist of the idea is to have a wrapper class that saves the first
currentDestinationId into a
ViewModel and when another navigation event is called, have it check the savedCurrentDestinationId against the actual
currentDestination.id, before delegating it to the actual NavController.
This operation relies on the assumption that when the first navigate is called the
currentDestination.id is correct. Don’t worry, if it’s not, you’ll still get the IllegalArgumentException.
Well, like many other things, this convenience comes at a cost. Since this method requires a couple of object creations, this will somewhat trim back the performance of your app.
I hear you thinking “Is it worth it?” Well, in the cases I pursued this solution it sure did, but I urge you to try and tell me all about it in the comments. Now go code. ;)
Gergely Hegedüs is an Android developer at Halcyon Mobile, a full-service mobile app design and development agency that creates award-winning mobile products for bold startups and brands.