Navigating with Confidence with Prism.Forms
Background:
I was writing a project in Xamarin.Forms recently and I really wanted to try out the Prism framework. I was excited to do so -- it's open source, part of the .NET foundation, has good community and team support, and was recommended by Miguel de Icaza at Xamarin Evolve 2016.
For the most part I've really enjoyed using Prism but there has been one thing bothering me on a personal preference and refactorability level: the navigation.
If you haven't used Prism before that might sound like a big deal; navigation is a huge part of how we think about and architect mobile applications. I should say, however, that the system Prism has in place is really, really great with support for url based navigation and query string arguments so it's ready to go for robust deep linking out of the gate.
My problem with the magic of that url navigation is strings. It might not irk others as much as it does me but I really try to stay away from "magic strings" when I can to keep my code a bit more resilient to refactoring and typos. Adding extensions on top of the string-based navigation system doesn't help too much with type safety but at least if I rename something or am looking for something in intellisense I feel like I won't waste quite as much time dealing with runtime crashes from silly mistakes while iterating.
I'll share what I've come up with (so far) below but I think I should point out one (or two) last thing(s) -- Prism is a large framework that supports WPF, Windows 10 UWP, and Xamarin Forms applications. I was just building a mobile application here (iOS/Android), so it made sense to make some extensions for my convenience but the system they've built out is much more robust for building applications that cross several platforms and form factors and I think that (among other things) more than justifies their string-based navigation. It's worth noting that my solution relies on a decent amount of Reflection which has its own performance issues. This was done purely out of preference.
Url Navigation:
You should read the Prism docs to get a full picture but the idea is that using your Page names you can create navigation stacks with simple URLs. For instance, "/LandingPage/ContactsPage/ContactDetailPage" would create a nav stack leading up to a ContactDetailPage and giving an Absolute URL like this would set your entire application to that stack (while using a relative URL would just append the pages to your current stack).
What I did first was create a INavTransaction
interface and NavTransaction
class (ignore the bundle object for now, we'll get to it later):
Pretty simple! Each NavTransaction
will have a Type argument of the Page we want to navigate to and we'll use that to generate the URLs for navigation within Prism's NavigationService
. I am currently just using a simple helper class to do just that:
This helper is pretty self-explanatory -- just accept an arbitrary amount of NavTransaction
objects and create a Uri string using the type Name (Note: I'm using MoreLINQ here for the simple ForEach
).
That's really it for making the URL navigation a bit more friendly for my preference. Here's a sample of how you might use that with Prism's NavigationService to create the same absolute navigation sample above:
It's definitely a little long-winded looking but that's a sacrifice I was willing to make.
Passing data:
Okay, back to those bundle objects that are in the NavTransaction
objects. In Prism (seriously, read those docs they're short, well written, and more verbose) you can pass data to your navigation destination with query string like so (taken from Prism docs):
The NavigationParameters
object from Prism basically creates a dictionary that you can then use to look up your desired data in your destination ViewModel (using INavigationAware
):
Nice, right? It really is, I'm not trying to be an ass. It's just the casting and the strings that I want to abstract out. So let's do that.
Okay, that was a longer bit of code to swallow and is a work-in-progress for me but it's sufficient for my project at the moment. Let's take a look at what it looks like using these extension methods. Here's a quick sample of a relative navigation to a ContactDetail page using the NavTransaction
's bundle property:
Again, pretty simple if verbose. Here's what it looks like trying to get that data in the destination ViewModel:
Not bad, we got rid of all the strings and things should refactor a little more cleanly with this, I think. I do want to talk about something that was introduced in Prism 6.3, however, and that is KnownNavigationParameters
. Prism introduced some helpful properties to NavigationParameters
that can be seen over at Github. These properties are added to every NavigationParameter
dictionary. For now when resolving objects I am ignoring those properties which is why I have that method to check for and ignore any keys defined in that class (again, using reflection).
These additions have given me a little peace and confidence at the cost of some reflection. I'm interested to see if anyone wants to bash me for this or has any ideas for improvement!