Navigating Navigation: A Journey Using Hyperview

Hardin Gray
Instawork Engineering
8 min readMay 14, 2024

--

Photo by Deva Darshan on Unsplash

Over the past year, I worked on the biggest new feature in Hyperview (our server-driven mobile app framework) since its creation six years ago: navigation.

As Instawork’s products matured, we wanted to evolve the information architecture of our mobile apps to accommodate new features. This proved difficult: although Hyperview supports behaviors to navigate between screens, the navigation structure itself was defined statically outside of the Hyperview framework. Experimenting with alternative navigation layouts required writing custom JS and releasing a full app update. Clearly, we needed to bring first-class navigation support to Hyperview, built on the ideas of hypermedia to provide a seamless developer experience.

I’m happy to announce that Hyperview now fully supports app navigation, configured using declarative HXML from the server. The implementation is built on top of React Navigation, and makes it easier than ever for developers to define navigation hierarchies and deep links. In this post, I’ll introduce the core concepts of mobile app navigation, the challenges we faced, and the solution we came up with.

Navigation concepts

Before we dive into the details of our implementation, let’s take a step back to understand some general concepts of navigation within mobile apps. For more in-depth explanations, see our Navigation Guide and the documentation on React Navigation’s site.

A navigator is an entity that provides a navigation state along with functionality to allow a user to switch to different screens. Various types of navigators support different ways of viewing and transitioning between screens:

  • Tab navigators use a set of tabs along the bottom of the application to allow a user to navigate directly to key areas of the application such as “Home” or “Profile”.
  • Stack navigators allow the user to dig into a series of screens and then back out of them in order. Unlike tab navigators, new screens can be added dynamically to the stack.
Figure 1: Example tab navigator on a mobile device.
Figure 2: Example stack navigator on a mobile device.

A route is a single view within a navigator (think “screen”). In the above examples, “home”, “profile”, “feed”, “help” and “topic” are all routes. In tab navigators, a route is focused based on the tab selected by the user. In stack navigators, new routes are added to the stack using a specific type of presentation that depends on the platform OS. For example, on iOS, “card” presentations slide in from right to left, and “modal” presentations slide in from the bottom to interrupt the flow.

Nested navigators use composition to create the hierarchy needed for more complex applications. For example, an application may use a stack navigator at the top level, and contain a tab navigator within the stack. The routes within the tab navigator might be stack navigators to drill down into the content of the tab.

The state of a navigator represents the currently focused route and the dynamically added routes contained by the navigator. In tab navigators, the state is the selected tab (and corresponding route). In stack navigators, the state tracks two pieces of information:

  1. the sequence of routes in the stack. The last route added to the stack is the currently focused route.
  2. The presentation style of each route in the stack (“card” or “modal”) route.

Deep linking provides the ability to navigate a user to a particular area of an application. Deep links may be triggered in one of two ways:

  • Launching the application with a deep link. Since there is no existing navigation state, the deep link provides the entire navigation structure and state.
  • Receiving a deep link while the application is running. In this case, there is already a navigation state so the new document needs to either replace or merge with the existing state.

The challenges

Supporting these mobile navigation concepts in a hypermedia-driven application provided some unique challenges. Since the navigation structure is now dynamically generated from the server, we can’t always know the full navigation structure ahead of time. These are some of the use cases we need to support:

  • A first time user gets a simplified set of tabs. They are provided only the parts of the application which they need to create their account.
  • A returning user is provided with the entire set of tabs to freely access the entire application.
  • A user launches the application with a deep link. The deep link must create the appropriate starting point for the user.
  • A user clicks a deep link while the application is open. The deep link must manage both the user’s existing state and the updates required by the link.

The solution

The first step in adding navigation into our HXML syntax was determining how to represent the different navigation concepts. There was no reason to overcomplicate the naming or structures, so we stayed close to the terminology discussed above. When our configuration requires a navigator, we use the new <navigator> element and tell it whether to create a tab or a stack navigator. For each route within the navigator, we use the <nav-route> element and pass an href or a nested <navigator>. The <nav-route> can also tell Hyperview whether to present the route as a modal.

Examples

Now that we have the building blocks to define navigation in HXML, let’s take a look at how we can describe navigation hierarchies. In the following examples, we’ll create a stack navigator with a “home” route, and a tab navigator with three routes.

<doc xmlns="https://hyperview.org/hyperview">
<navigator id="root" type="stack">
<nav-route id="home" href="http://myapp.com/home.xml" />
</navigator>
</doc>

Example 1: Simple stack navigator.

With this basic structure, we have instructed Hyperview to create a stack navigator and to load the file “home.xml” into the route.

Figure 3: Diagram of a simple stack navigator. The navigator is colored in red and the route in green.
<doc xmlns="https://hyperview.org/hyperview">
<navigator id="root" type="tab">
<nav-route id="home" href="http://myapp.com/home.xml" />
<nav-route id="profile" href="http://myapp.com/profile.xml" />
<nav-route id="feed" href="http://myapp.com/feed.xml" />
</navigator>
</doc>

Example 2: A simple tab navigator with the three routes “home”, “profile” and “feed” representing the screens associated with each tab.

Figure 4: Diagram of a simple tab navigator.

Complex hierarchies

Some applications require more complex navigation and for this we support nesting navigators inside other navigators. This nesting can be defined within a single document or can be separated into multiple documents. The nesting is provided as a function of the <nav-route> element.

<doc xmlns="https://hyperview.org/hyperview">
<navigator id="root" type="stack">
<nav-route id="tabs">
<navigator id="main" type="tab">
<nav-route id="home" href="home.xml" />
<nav-route id="profile" href="profile.xml" />
<nav-route id="feed" href="feed.xml" />
</navigator>
</nav-route>
</navigator>
</doc>

Example 3: A tab navigator nested inside a route within a stack navigator.

Figure 5: Visualization of a complex hierarchy with nested navigators.

Navigation state

So far we’ve shown how complex navigation structures can be defined in HXML. But we also need a way to represent the navigation state. As discussed earlier, the navigation state needs to represent the selected tab in a tab navigator, and the sequence of routes (plus their presentation mode) for stack navigators.

To represent the selected tab in a tab navigator, we use selected=”true” on the selected nav route:

<doc xmlns="https://hyperview.org/hyperview">
<navigator id="root" type="tab">
<nav-route id="home" href="home.xml" />
<nav-route id="profile" href="profile.xml" selected="true" />
<nav-route id="feed" href="feed.xml" />
</navigator>
</doc>

Example 4: The second tab will be selected when the user launches the application.

To represent the sequence of routes in a stack navigator, we simply include each route in the HXML. The order of the routes indicates the sequence, with the last one being the focused route. The presentation of the routes in the stack navigator is defined with the modal=true attribute.

<doc xmlns="https://hyperview.org/hyperview">
<navigator id="root" type="stack">
<nav-route id="tabs" href="tabs.xml" />
<nav-route id="modal" href="help.xml" modal="true" />
</navigator>
</doc>

Example 5: Pre-populated stack navigator with a modal route.

In this example of stack navigator state, the stack already contains two routes. The user currently sees “help.xml” as a modal. When they close this screen, they will reveal the “tabs.xml” screen.

Deep links

Now that we have a declarative syntax that represents both the structure and state of navigation, the implementation of deep links becomes obvious: we can deep link to any part of the app by swapping out the current navigation doc with a new one. The new doc can change the state at any point in the hierarchy, including switching tabs and changing the routes in a stack. Each time a new navigation doc is provided through a deep link, Hyperview will update and re-render the navigation structure. The navigation state in the new document will either reset or modify the existing state. See the deep link section of our Navigation Guide for more details on handling deep links.

Use cases

The flexible way our HXML elements describe both the navigation structure and the state allows a wide range of use cases including:

  • Create a stack which is already populated with several screens
  • Change the navigation options based on business logic
  • Select a default tab for the user
  • Change the user’s focus to a new screen
  • Open (or close) a modal

What’s next?

The new navigation features in Hyperview have been carefully thought out to maximize flexibility while maintaining a simple syntax. They offer the ability to dynamically define and alter the navigation hierarchy of any application to best suit the needs of the product. We are continuing to refine the features of Hyperview navigation and are evaluating future improvements. Meanwhile, we are busily updating our own applications to take advantage of all of the powerful new features. It is one of the best ways we can be sure Hyperview remains a useful tool for developers.

Our Navigation Guide includes more details, examples and best practices for incorporating navigation into your projects. Please let us know if you have any suggestions or questions about Hyperview navigation.

Special thanks to Adam Stepinski and @flochtililoch who were invaluable through the entire project.

--

--