Hyperview

Hyperview

  • Guides
  • Examples
  • Reference
  • Blog

›Recent Posts

Recent Posts

  • Events in Hyperview
  • April Update: Custom behaviors and better debugging
  • When XML beats JSON: Extensibility
  • When XML beats JSON: UI layouts
  • 0.7 Released: Support for system alerts

Events in Hyperview

August 1, 2019

Divyanshu Maithani

We recently added an elements management system with events in Hyperview PR #81. This makes it easier to communicate between different Hyperview screens. Thanks to adamstep and flochtililoch for valuable feedback and coming up with the API design.

Problem

Quite often a change in one screen can result in a change in an existing screen in the navigation stack. For example, consider a screen containing user feeds. Clicking on a post in the feeds screen opens a new screen which displays the post. Clicking on "like" should also update the like count in the previous screen which is already pushed on the navigation stack. This is a good use case for using the elements management system.

Spec

This feature introduces a new trigger on-event and a new behavior action dispatch-event. (Refer to the example for complete XML)

Usage

Consider the following view from dispatch_event.xml rendered on the screen:

<view action="append" trigger="on-event" event-name="test-event" scroll="true" href="/advanced_behaviors/dispatch_event/dispatch_event_append.xml" style="Main">
  <text style="Description">Dispatch events to load screens</text>
  <view style="Button" href="/advanced_behaviors/dispatch_event/dispatch_event_source.xml">
  <text style="Button__Label">Open new screen</text>
</view>

This view will listen for on-event trigger specifically for the event-name test-event. When this event is caught, it does the append action and appends the XML fragment dispatch_event_append.xml which looks like:

<text xmlns="https://hyperview.org/hyperview">This will get appended with events!</text>

The source of this event is a different screen:

<view style="Button">
  <behavior trigger="press" action="dispatch-event" event-name="test-event" />
  <text style="Button__Label">Reload previous</text>
</view>

With the press trigger the dispatch-event action will fire an event (internal to hyperview) with the name defined by the event-name attribute.

Extending

on-event and dispatch-event also supports once and delay.

Extending with once

  • When added to a listener behavior (with trigger="on-event"), the behavior would only respond to the dispatched event once (till the screen is unmounted)
  • When added to the dispatching behavior (with action="dispatch-event"), the behavior will only dispatch the event once

Extending with delay

  • When added to a listener behavior (with trigger="on-event"), the behavior would respond to the dispatched event after the specified delay (in milliseconds)
  • When added to the dispatching behavior (with action="dispatch-event"), the behavior will dispatch the event after the specified delay (in milliseconds)

Examples

Some more examples where events fit in are:

  1. When actions on one screen might affect another screen, for example interacting with an element might update some element in another screen which is beneath the current screen in the navigation stack.

  2. Events can help in decoupling UI elements. A dispatching element doesn't need to know about the receiver element (for example target id). This way, UI elements can be responsive (keep their behaviors nested under the element, rather than in a different place).

Debugging tip

In __DEV__ mode, the console will log the element which captures an event as well as the element which dispatches it.

Please check the behavior attributes docs for more info.

April Update: Custom behaviors and better debugging

April 21, 2019

Adam Stepinski

Hyperview v.0.12.2 contains many new features and enhancements since our last update, the biggest one being support for custom behaviors. With support for both custom elements and custom behaviors, the space of possibilities for Hyperview apps is huge!

Thanks to divyanshu013, flochtililoch, utkbansal, and dikarel for their great contributions to this release.

New Features

  • Hyperview now fully supports custom behaviors. As part of this change, we've removed any behaviors that required external dependencies (phone calls, event tracking, etc). These behaviors were specific to Instawork, so it wasn't appropriate to include them in the core framework.
  • Behaviors can now trigger a reload action, which mimics the functionality of a refresh button in a browser. This is often useful during development to quickly refresh a single screen in an app.
  • A new <web-view> element allows embedding web view screens in a Hyperview app.
  • A new auto-focus attribute on <text-field> allows focusing a field as soon as it renders on screen.

Development Experience

Better logging messages

Based on feedback from Hyperview developers, we added helpful debug logging:

  • The use of unregistered elements will show a warning in the console.
  • The use of unregistered behaviors will show a warning in the console.
  • When a behavior modifies the DOM, the new DOM for the screen gets logged to the console.

Watch mode for demo

Previously, when implementing features in Hyperview, the demo app had to be re-built with every change. There's now a "watch" mode that will automatically propagate changes to the demo app.

Reloading single screens

Behaviors can now trigger a "reload" action, which mimics the functionality of a refresh button in a browser. This is often useful during development to quickly refresh a single screen in an app.

Removal of animation system

Before version 0.12.2, Hyperview had a beta implementation of an animation system. We removed this system to simplify the client, and to lay the groundwork for a fuller animation/gesture system in the future.

Bug fixes

  • Behaviors are no longer detected solely by the presence of an href attribute. Now, the presence of an href or action attribute will correctly identify

  • On Android, sending empty multi-part forms no longer causes crashes.

When XML beats JSON: Extensibility

February 10, 2019

Adam Stepinski

In Part 1 of this series of blog posts, we explored the relative strengths and weaknesses of JSON and XML when representing various data structures. For key-value based data, JSON is the clear winner. But XML beats JSON when it comes to representing tree structures. Since UI layouts are commonly represented as trees, XML is the natural choice for a UI framework like Hyperview.

Another imporant consideration for Hyperview was extensibility. Extensibility matters for a couple of reasons:

  • We want to keep the core UI elements limited to the bare essentials. But we don't want the framework to be limited to only those core elements. We wouldn't include a map UI element in the core, but it should be possible for developers to add a customed map element to their Hyperview app.
  • Developers should have the freedom to integrate with services or technologies they already use. Some developers may use Mixpanel for tracking UI events, others may use Amplitude or an in-house system. Hyperview should remain agnostic and support all of these cases.

What's the X for?

XML stands for Extensible Markup Language, so it should come as no surprise that extensibility is built into the lowest level of the spec. The extensibility of XML is based on the idea of using multiple XML "vocabularies" within one XML document. A vocabulary is simply a collection of tags and attributes with a specific meaning. Vocabularies can be formalized with a schema definition and embedded into XML documents via namespaces.

To illustrate how we can embed different vocabularies in the same XML document, let's use the map example from above. Hyperview doesn't come with a way to describe a map, but we can define our own vocabulary:

  • <map> will represent a specific instance of a map. It takes a few attributes:
    • latitude: the latitude of the upper left corner of the map
    • longitude: the longitude of the upper left corner of the map
    • latitude-delta: the latitudinal length covered by the map
    • longitude-delta: the longitudinal length covered by the map
  • <marker> will represent a indicator marker placed on the map. The attributes:
    • latitude: the latitude of the marker
    • longitude: the longitude of the marker

Using this new vocabulary, we can describe a map containing two markers:

<map latitude="37.65" longitude="-122.50" latitude-delta="0.1" longitude-delta="0.05">
  <marker latitude="37.65" longitude="-122.46"/>
  <marker latitude="37.64" longitude="-122.46"/>
</map>

To embed this map within a Hyperview screen, we need to give the vocabulary a namespace name. The namespace name should be a unique URI, so it's common to use the organization's web domain. For our map vocabulary, we will use https://instawork.com/xml/map. We can now declare the namespace in our Hyperview doc, assign it a prefix, and use that prefix for our elements:

<doc
    xmlns="https://hyperview.org/hyperview"
    xmlns:map="https://instawork.com/xml/map"
>
  <screen>
    <body>
      <text>This is my map!</text>
      <map:map latitude="37.65" longitude="-122.50" latitude-delta="0.1" longitude-delta="0.05">
        <map:marker latitude="37.65" longitude="-122.46"/>
        <map:marker latitude="37.64" longitude="-122.46"/>
      </map:map>
    </body>
  </screen>
</doc>

Let's break this down:

  • In the <doc> tag, we've added the attribute xmlns:map="https://instawork.com/xml/map". This declares that tags prefixed with "map" belong to the namespace https://instawork.com/xml/map.
  • Within the <body> tag, we've added our map, but each tag is prefixed with map:.
  • Note that the Hyperview tags don't require a prefix. That's because the <doc> attribute xmlns="https://hyperview.org/hyperview" declares Hyperview as the default namespace in the doc. Any unprefixed tag will be interpreted as part of the default namespace.

As you can see, namespaces support extensibility by allowing multiple XML vocabularies to co-exist and mix together in one document. APIs that process XML, such as the DOM, have built-in support for searching and querying in a namespace-aware way. This makes it easy to create a framework that can call out to plugins or registered classes when processing tags in a certain namespace.

In fact, that's exactly how custom elements work in Hyperview. Custom elements simply map a namespace and tag name to a React Native component written by the developer. When rendering a screen, if Hyperview comes across a namespaced tag, it looks up the associated component and renders it. Here's the kicker: Hyperview's core UI elements work the same way, there's no need for special handling!

Why reinvent the wheel?

So by design, XML is highly extensible, but what about JSON? Well, there's not much to say since JSON doesn't have any native concept of namespaces or extensibility. Everything is one big nested data structure of arrays and dictionaries. Of course, it's possible to replicate the concept in JSON, maybe with a special $namespace property on an object, or a <namespace>:<prop> convention for objects keys. But JSON libraries would have no awareness that these properties have a semantic meaning, requiring developers to write a lot of special handling for this syntax.

At Instawork, we prefer to stand on the shoulders of giants rather than reinvent the wheel.

So when it came to creating Hyperview, we looked for well-understood, battle-tested tools that matched our needs. With XML, we knew we could evolve and extend the Hyperview spec using the proven technique of XML vocabularies and namespaces. This freed us up to focus on what's new and exciting in Hyperview: the ability to express the UI and interactions of today's mobile apps in a purely declarative way.

Stay tuned for Part 3 in this series, where we'll look at some of the practical & ergonomic reasons we chose XML over JSON for Hyperview's data format.

When XML beats JSON: UI layouts

January 16, 2019

Adam Stepinski

When demoing Hyperview to new engineers, there's one comment that frequently comes up about the HXML data format:

"XML, really? It's bloated and outdated. Why not use JSON? It's the future."

These comments imply that JSON is the "one true file format" that should be used for everything, but we don't believe there's such a thing. Each format makes tradeoffs in encoding, flexibility, and expressiveness to best suit a specific use case.

  • A format optimized for size will use a binary encoding that won't be human-readable.
  • A format optimized for extensibility will take longer to decode than a format designed for a narrow use case.
  • A format designed for flat data (like CSV) will struggle to represent nested data.

In addition to the intrinsic properties of a format, external factors can influence its practical use cases, such as the popularity of the format among the target developers, or library support in the desired programming languages.

When choosing a file format to use in a software project, software engineers pick the one with the best balance of features and external factors for the situation. This post is the first in a 3-part series examining the decision process we used to choose XML over JSON as the format at the core of Hyperview. In part 1, we focus on the suitability of each format for defining user interfaces.

JSON for lists, XML for trees

Both XML and JSON can represent complex nested data structures, but they excel at different types of structures. JSON's origins as a subset of JavaScript can be seen with how easily it represents key/value object data. XML, on the other hand, optimizes for document tree structures, by cleanly separating node data (attributes) from child data (elements).

We can illustrate the advantages and disadvantages of each format by using both formats to represent two types of data: lists and trees.

Example 1: List of users

Let's represent a list of users. Each user has a first and last name, and a list of favorite movies. In JSON, there's a sensible way to model this data: as an array of objects with keys for the user properties:

[
  {
    "first_name": "Michael",
    "last_name": "Scott",
    "favorite_movies": [ "Diehard", "Threat Level Midnight" ]
  },
  {
    "first_name": "Dwight",
    "last_name": "Schrute",
    "favorite_movies": [ "The Crow", "Wedding Crashers" ]
  },
  {
    "first_name": "Pam",
    "last_name": "Beesly",
    "favorite_movies": [ "Legally Blonde" ]
  }
]

A particular strength of JSON is its support for nested data structures. Notice that the list of movies can be easily represented using an array value for the favorite_movies key. In XML, however, element attributes must be strings. This means there's no officially supported way to represent the list of movies in an element attribute. We can hack this by encoding the list into an attribute using a comma delimiter:

<Users>
  <User first="Michael" last="Scott" favoriteMovies="Diehard, Threat Level Midnight" />
  <User first="Dwight" last="Schrute" favoriteMovies="The Crow, Wedding Crashers" />
  <User first="Pam" last="Beesly" favoriteMovies="Legally Blonde" />
</Users>

This solution is not ideal. We're not using XML semantics to represent the list of movies, meaning we can't rely on XML syntax validation to verify the data. The clients reading the data will need to know how to split on commas and unescape the data in this particular attribtue. So both writing and reading the data is error-prone. To address these shortcomings, we should use XML syntax to represent the list of movies. This means creating child elements for each movie:

<Users>
  <User first="Michael" last="Scott">
    <Movie>Diehard</Movie>
    <Movie>Threat Level Midnight</Movie>
  </User>
  <User first="Dwight" last="Schrute">
    <Movie>The Crow</Movie>
    <Movie>Wedding Crashers</Movie>
  </User>
  <User first="Pam" last="Beesly">
    <Movie>Legally Blonde</Movie>
  </User>
</Users>

With this approach, we avoid the hack of using a comma delimiter in an attribute. However, we've created another problem in the form of inconsistency: some user properties are represented as element attributes, others as child elements. The client has to know to look in both places to get all of the user's data. To be consistent, we can represent all user properties as child elements:

<Users>
  <User>
    <FirstName>Michael</FirstName>
    <LastName>Scott</LastName>
    <FavoriteMovies>
      <Movie>Diehard</Movie>
      <Movie>Threat Level Midnight</Movie>
    </FavoriteMovies>
  </User>
  <User>
    <FirstName>Dwight</FirstName>
    <LastName>Schrute</LastName>
    <FavoriteMovies>
      <Movie>The Crow</Movie>
      <Movie>Wedding Crashers</Movie>
    </FavoriteMovies>
  </User>
  <User>
    <FirstName>Pam</FirstName>
    <LastName>Beesly</LastName>
    <FavoriteMovies>
      <Movie>Legally Blonde</Movie>
    </FavoriteMovies>
  </User>
</Users>

Now we have a consistent, extensible representation for a list of users in XML. But compared to the same data in JSON, this representation is overly verbose and hard to write. The XML syntax obscures the structure of the data.

So JSON is well-suited for representing lists of objects with complex properties. JSON's key/value object syntax makes it easy. By contrast, XML's attribute syntax only works for simple data types. Using child elements to represent complex properties can lead to inconsistencies or unnecessary verbosity.

Example 2: Org Chart

XML may not be ideal to represent generic data structures, but it excels at representing one particular structure: the tree. By separating node data (attributes) from parent/child relationships, the tree structure of the data shines through, and the code to process the data can be quite elegant.

A classic example of a tree structure is a company org chart. Let's assume each node in the tree is an employee, with a name and title. The edges in the tree represent a reporting relationship. The root of the tree is the boss.

In JSON, we represent each employee as an object with key/value pairs for the name and title. But JSON doesn't provide a separate notation for relationships vs attributes, so we must also use a key/value pair to represent an employee's direct reports:

{
  "name": "Michael Scott",
  "title": "Regional Manager",
  "reports": [
    {
      "name": "Dwight Schrute",
      "title": "Assistant to the Regional Manager"
    },
    {
      "name": "Jim Halpert",
      "title": "Head of Sales",
      "reports": [
        {
          "name": "Andy Bernard",
          "title": "Sales Rep"
        },
        {
          "name": "Phyllis Lapin",
          "title": "Sales Rep"
        }
      ]
    },
    {
      "name": "Pam Beesly",
      "title": "Office Administrator"
    }
  ]
}

Notice that it's hard to distinguish a node's properties from the relationships. It's common to prefix the relationship keys with "$" or "_" to make the distinction clearer, but this is a hack akin to using a delimiter in an XML attribute. When writing the JSON, we can cause errors by forgetting the prefix. Likewise when reading the JSON, we can't loop through the keys in an object without remembering to filter out the ones starting with the prefix. Both reading and writing the data is now error-prone and inelegant.

XML really shines when it comes to tree data: elements are nodes, attributes are node properties, and child elements imply the reporting relationship:

<Employee name="Michael Scott" title="Regional Manager">
  <Employee name="Dwight Schrute" title="Assistant to the Regional Manager" />
  <Employee name="Jim Halpert" title="Head of Sales">
    <Employee name="Andy Bernard" title="Sales Rep" />
    <Employee name="Phyllis Lapin" title="Sales Rep" />
  </Employee>
  <Employee name="Pam Beesly" title="Office Administrator" />
</Employee>

Compared to the JSON example, it's much easier to get a sense of the underlying structure of the data. Attributes of the employee (name and title) are cleanly separated from the employee relationships. The relationships are both easier to visualize when writing the file, and the code to read the data can operate on the overall structure without touching node properties. There's no chance of accidentally interpreting the relationship data as an employee attribute like in the JSON example.

The advantages of XML over JSON for trees becomes more pronounced when we introduce different node types. Assume we wanted to introduce departments into the org chart above. In XML, we can just use an element with a new tag name:

<Department name="Scranton Branch">
  <Employee name="Michael Scott" title="Regional Manager">
    <Department name="Sales">
      <Employee name="Dwight Schrute" title="Assistant to the Regional Manager" />
      <Employee name="Jim Halpert" title="Head of Sales">
        <Employee name="Andy Bernard" title="Sales Rep" />
        <Employee name="Phyllis Lapin" title="Sales Rep" />
      </Employee>
    </Department>
    <Employee name="Pam Beesly" title="Office Administrator" />
  </Employee>
</Department>

JSON doesn't have a concept of node types, so again we need to introduce a new special prefixed key to represent the type:

{
  "$type": "department",
  "name": "Scranton Branch",
  {
    "$type": "employee",
    "name": "Michael Scott",
    "title": "Regional Manager",
    "$reports": [
      {
        "$type": "department",
        "name": "Sales",
        "$reports": {
          "$type": "employee",
          "name": "Dwight Schrute",
          "title": "Assistant to the Regional Manager"
        },
        {
          "$type": "employee",
          "name": "Jim Halpert",
          "title": "Head of Sales",
          "$reports": [
            {
              "$type": "employee",
              "name": "Andy Bernard",
              "title": "Sales Rep"
            },
            {
              "$type": "employee",
              "name": "Phyllis Lapin",
              "title": "Sales Rep"
            }
          ]
        },
      },
      {
        "$type": "employee",
        "name": "Pam Beesly",
        "title": "Office Administrator"
      }
    ]
  }
}

Now it's JSON's turn to be bloated and hard-to-read. Meanwhile, the XML version represents the tree data succinctly without obscuring the underlying structure.

These two examples highlight the relative strengths and weaknesses of JSON and XML when representing different types of data:

  • JSON excels at representing a collection of homogenous objects, where object properties can be composite data types
  • XML excels at representing trees with heterogeneous objects, where object properties are simple data types

UI Layouts are trees

Hyperview's goal is to bring a web-like development experience to mobile:

  • On the web, a server responds to an HTTP request with HTML/CSS to render a web page.
  • In Hyperview, a server responds to an HTTP request with a response to render a mobile app screen.

In order to fill the role of HTML/CSS for mobile apps, Hyperview's file format must be able to define the layout and structure of an app screen's UI. And component trees are the best way to represent UI layouts. Every major UI framework out there uses a component tree. Xcode even offers an exploded 3D view that really highlights the underlying tree structure of a screen: each component has a parent component, and everything rolls up to a shared root component.

Trees are a powerful representation for UI layouts. They naturally provide component groupings, allowing designers and developers to use higher-level abstractions. When we need to hide, show, or animate a section of the screen, we don't need to change the state of each component in the section. Rather, we can change the state of the single parent component that contains every UI element of the section. When modifying a component, we only need to worry about what's in its subtree, and not what's happening at higher levels.

XML for UI Layouts

UI layouts are represented as component trees. And XML is ideal for representing tree structures. It's a match made in heaven! In fact, the most popular UI frameworks in the world (HTML and Android) use XML syntax to define layouts. Still not convinced? Take a look at React, the popular JS library designed around composing UI components. If there was ever a library to embrace JSON for defining UI, it should be React, right? But the React website actually recommends defining component UI using JSX, an XML-like format. Even though using JSX requires learning a new non-JS syntax and adding a transpiling step to the build process, the library authors feel it's worth the pain in order to use XML!

As noted at the beginning, each data format comes with certain tradeoffs. Overall, the benefits of XML for Hyperview outweigh the downsides, we still need to handle those rough spots. In particular, we noted that XML element attributes cannot natively represent lists of data. This case occurs in Hyperview with how we apply styles to elements. Due to this limitation of XML, styles are represented as space-separated ids, and the ids cannot contain spaces. Luckily, this limitation matches the behavior of the class attribute in HTML, so the approach is at least familiar to web developers.

Stay tuned for parts 2 and 3 of our exploration of XML and JSON in the context of Hyperview. Part 2 will cover the extensibility of JSON and XML, while Part 3 will compare the developer experience of both formats.

0.7 Released: Support for system alerts

January 13, 2019

Adam Stepinski

Today we released Hyperview v0.7.0. This version includes one new feature: the ability to trigger native system alerts on iOS and Android.

Here's the HXML to render the alert above:

<behavior
  xmlns:alert="https://hyperview.org/hyperview-alert"
  trigger="longPress"
  action="alert"
  alert:title="This is the title"
  alert:message="This is the message"
>
  <alert:option
    alert:label="Screen 1"
    href="/screen1"
    action="push"
  />
  <alert:option
    alert:label="Screen 2"
    href="/screen2"
    action="new"
  />
</behavior>

Alerts are triggered using the standard behavior syntax. This means any Hyperview trigger can cause an alert to appear: press, longpress, load, refresh, etc. You could even trigger an alert on visible, but I wouldn't recommend it as a good user experience.

To prevent name clashes, the new attributes and elements use a namespace: https://hyperview.org/hyperview-alert. I find it's nice to define the namespace on the <behavior> that will show the alert (see the example above), rather than at the root of the doc. This will likely be a common pattern for new elements added to Hyperview.

The child <alert:option> elements define the available options on the alert. The options contain standard Hyperview behaviors that trigger on press. This means alert options can do things like navigate screens, update parts of the page, or execute custome behaviors.

Use cases

System alerts open up many new use cases in Hyperview, some common ones are explained below.

Confirmation before performing an action

Before performing a destructive action, like a DELETE request to the server, it can be a good idea to get confirmation from the user. To do the confirmation with a system alert, wrap the destructive behavior in an alert with a "Confirm" and "Cancel" option. The destructive behavior should be triggered when the user presses "Confirm". No behaviors need to be associated with the "Cancel" option, which will dismiss the alert.

<view style="Button">
  <behavior
    xmlns:alert="https://hyperview.org/hyperview-alert"
    action="alert"
    alert:title="Delete business?"
    alert:message="All job posts at this location will be permanently deleted."
  >
    <alert:option
      alert:label="Delete"
    >
      <behavior
        verb="DELETE"
        href="/businesses/123"
        action="replace"
        target="Main"
      />
    </alert:option>
    <alert:option
      alert:label="Cancel"
    />
  </behavior>

  <text style="Button__Text">
    Delete
  </text>
</view>

Status after performing an action

After performing a server-side action, alerts can be used to convey the status of the result. For example, if there's a problem validating the data or other server-side error, the response can include a behavior triggered on load that displays an alert with the error message. No alert options need to be provided, the system will have a default dismiss button.

It's best not to overuse alerts for statuses, since they require user interaction to dismiss. For less important status updates, such as positive confirmation, consider using a non-blocking toast or inline feedback.

<view style="Main">
  <behavior
    xmlns:alert="https://hyperview.org/hyperview-alert"
    trigger="load"
    action="alert"
    alert:title="Something went wrong"
    alert:message="The business could not be deleted. Please try again later."
  />
</view>

Show extra options for user actions

Sometimes a user action may have unclear intent. For example, when deleting a recurring event in a calendar app, it's unclear if the user means to delete just the event on one day, or all future events as well. In situations like this, an alert with the two options can disambiguate the user's intent. When using an alert for disambiguation, providing a "Cancel" option is a good ideas as well.

<view style="Button">
  <behavior
    xmlns:alert="https://hyperview.org/hyperview-alert"
    action="alert"
    alert:title="Delete future events?"
    alert:message="Do you want to delete all future events as well?"
  >
    <alert:option
      alert:label="Delete single event"
    >
      <behavior
        verb="DELETE"
        href="/events/123"
        action="replace"
        target="Main"
      />
    </alert:option>
    <alert:option
      alert:label="Delete all future event"
    >
      <behavior
        verb="DELETE"
        href="/events/123?delete_future=true"
        action="replace"
        target="Main"
      />
    </alert:option>
    <alert:option
      alert:label="Cancel"
    />
  </behavior>

  <text style="Button__Text">
    Delete
  </text>
</view>

Next steps

Check out the full documentation of alerts on the reference page. You can also play around with examples in the demo app or see the HXML samples in the Github repo.

The implementation of alerts demonstrates how behaviors can do more than navigations or screen updates, and how chaining behaviors together opens up new interaction possibilities. The pattern used for alerts will be used for other upcoming features, such as:

  • system permission dialogs
  • action sheets
  • prompts

Look for support for these behaviors in an upcoming release! Follow @hyperview_org on Twitter to hear about the latest news.

Introducing Hyperview: a server-driven mobile app framework

December 17, 2018

Adam Stepinski

In a recent blog post, I explained why the Instawork engineering team moved our web development from the popular SPA + API architecture to server-side rendered web pages. Server-side rendering lets us develop new features quickly using a single language (Python) and a single integrated framework (Django). Adding Intercooler.js to the mix gives us SPA-like interactivity, while still keeping all business logic and HTML rendering on the server.

The level of productivity we were able to achieve with Django + Intercooler was incredible, but the impact was relatively small. That's because Instawork's main platform is not the web, but mobile. The vast majority of our users access Instawork through native iOS and Android apps. And on mobile, we were still using the very architecture we just ditched on the web: a thick client (written in React Native) pulling data through a JSON API.

Thick clients are the industry-standard architecture for native mobile apps, but we started questioning why it seemed to be the only option. Thick clients make sense for some categories of apps like games, photo tools, or anything that relies on local state & data. But a thick client is not a great fit for networked apps like marketplaces or social networks, where an Internect connection is a requirement for the app to function. The root of the problem:

Implementing a networked app as a thick client requires business logic to be split and duplicated between the backend and frontend.

  • A thick client needs to know the possible API endpoints and JSON format of those endpoints, resulting in duplicated code.
  • A thick client contains logic determining how to validate user input and what kind of inputs to ask for. The server also needs to perform this validation, resulting in more duplicated code.
  • A thick client contains conditional logic to determine how to render API data and how to navigate between screens based on user interactions or backend responses. This critical logic exists exclusively on the frontend.

The code duplications add complexity and development time, since the same logic needs to be implemented twice. The frontend/backend splits add headaches once the userbase is on multiple app versions, with each version making slightly different assumptions but all interacting with the same backend.

Once we realized that networked apps are a poor fit for thick clients, we started looking for a thin-client mobile framework to adopt. The best solution out there seemed to be HTML + JS web apps bundled in a native wrapper (such as Cordova). We found this solution to be a little clunky for our needs: HTML was not designed with mobile UIs in mind, resulting in poor performance and less than ideal UX.

We didn't find an existing thin-client framework for native mobile apps. So we built our own.

Instawork is excited to announce Hyperview, our open-source framework for developing mobile apps using the same thin-client architecture that powers the web. Hyperview consists of two parts:

Hyperview XML (HXML): a new XML-based format for describing mobile app screens and user interactions. HXML's building blocks reflect the UI patterns of today's mobile interfaces. Features like stack & modal navigation or infinite-scrolling lists can be defined with a few simple XML attributes and tags.

Hyperview Client: a mobile library (on top of React Native) that can render HXML. The client can render any HXML screen and handle navigations & interactions by making further HXML requests.

On the web, pages are rendered in a browser by fetching HTML.

With Hyperview, screens are rendered in your mobile app by fetching Hyperview XML (HXML).

HXML

In HXML, mobile app screens are defined in XML format using a core set of attributes and tags. The experience writing HXML should feel familiar to anyone who's worked with HTML:

<doc xmlns="https://hyperview.org/hyperview">
  <screen>
    <body>
      <text>Hello, Hyperview!</text>
    </body>
  </screen>
</doc>

But there are some significant differences between the two formats. HTML was designed to represent static text documents. HXML, on the other hand, is designed to represent dynamic mobile apps.

Native syntax for common mobile UI elements

HXML has tags for common mobile UI patterns, like section lists or headers. This makes it easy to create a list with sticky sectioned headers, without the need for scripting or tricky CSS:

<section-list>
  <section>
    <section-title><text>Section 1</text></section-title>
    <item key="1"><text>Item 1</text></item>
    <item key="2"><text>Item 2</text></item>
  </section>

  <section>
    <section-title><text>Section 2</text></section-title>
    <item key="3"><text>Item 3</text></item>
    <item key="4"><text>Item 4</text></item>
  </section>
</section-list>

Custom input styling

HXML's input elements allow complete control over styling to create more customized forms. In HTML, input elements are limited to certain appearances and semantics, like "radio" or "checkbox". In HXML, flexible elements like <select-multiple> and <option> can be rendered as checkboxes, tags, images, or any other appropriate visual treatment.

Rich interactions without scripting

In HTML, the basic user interaction is the hyperlink: users click an element, and the browser requests a new page.

<a href="/next-page">Behold, the power of the hyperlink!</a>

Hyperlinks on their own are not expressive enough to build interactive web apps, so developers use Javascript to create more complex interactions.

In HXML, the behavior syntax can express a large class of user interactions with declarative markup.

  • The trigger attribute allows behaviors to execute not just on press, but under many other types of user interactions, such as a pull-to-refresh.
  • The action attribute allows a behavior to navigate to a new screen. But it also supports modifying the current screen by replacing, appending, or prepending HXML content to elements. action gives developers the power of AJAX without needing to write callbacks or promises in code.
  • Declarative loading states can hide or show parts of the screen while requests are in-flight.

Take this example HXML snippet:

<body id="Body">
  <list>
    <behavior trigger="refresh" href="/news" action="replace" target="Body" />
    <item key="1" href="/news/1">
      <behavior trigger="press" action="push" href="/news/1" />
      <behavior trigger="longPress" action="new" href="/news/1/settings" />
      <text>Story 1</text>
    </item>
  </list>
</body>

The example contains three <behavior> elements, which declare (respectively):

  • When the user does a pull-to-refresh gesture on the list, make an HTTP request to fetch /news, and replace the element with id Body with the HXML response content.
  • When the user presses the item, make an HTTP request to fetch /news/1, and show the content in a new screen pushed onto the stack.
  • When the user long-presses the item, make an HTTP request to fetch /news/1/settings, and show the content in a new modal above the current stack.

With HXML, it's possible to create a rich mobile UI in pure XML, without the need for client-side scripting. In fact, HXML doesn't even support scripting, meaning all state and business logic remains in one place on the server.

Hyperview client

The Hyperview client library exposes a Hyperview React Native component that can be added to the navigation of an existing React Native app. The most important prop passed to the Hyperview component is entrypointUrl. When the component mounts, it will make a request to the entrypoint URL, expecting HXML in the response. Once the client receives the response, the HXML gets rendered on the current screen. Behaviors in the HXML will result in navigations or updates to the current screen. The client handles these behaviors by making subsequent requests for new bits of HXML, and the cycle can continue indefinitely.

In other words, think of the Hyperview client like a web browser, but for HXML. The entrypoint URL is like a hard-coded address in the toolbar, pointing at your homepage. The homepage contains links and forms that result in more requests for more HXML. The true power of the thin client becomes apparent when you realize that with a single entrypoint URL and server-driven layouts, there are endless possibilities to add and modify screens and interactions in your mobile app.

There's much more to the Hyperview client, including the ability to extend its capabilities by registering custom elements and custom behaviors. These custom elements and behaviors are like browser plugins that can provide deeper integration with your existing app or with OS capabilities like phone calls, mapping, etc.

Hyperview in production

Over the last 6 months, Instawork has used Hyperview in our mobile apps to implement dozens of screens. The experience has been fantastic, and we're doubling down on our use of Hyperview in more of our core app flows. Here are just some of the advantages we've already experienced:

Focus on features, not architecture

In a thick client/JSON API architecture, developers need to make many architectural decisions when building a feature:

  • How do I store the data for my feature?
  • Do I extend an existing API endpoint and resource or create a new one?
  • How do I model the user interactions as HTTP requests?
  • Will my changes impact the performance of other clients using the same API?
  • Is my API design generic enough to serve use-cases beyond the current feature I'm building?
  • In order to make the design generic, am I exposing too much data?
  • Are my API changes compatible with older client versions?
  • What does the UI look like?

With Hyperview, the API-related architecture decisions go away, and the developer can focus on the important parts of the feature:

  • How do I store the data for my feature?
  • Do I extend an existing API endpoint and resource or create a new one?
  • How do I model the user interactions as HTTP requests?
  • Will my changes impact the performance of other clients using the same API?
  • Is my API design generic enough to serve use-cases beyond the current feature I'm building?
  • In order to make the design generic, am I exposing too much data?
  • Are my API changes compatible with older client versions?
  • What does the UI look like?

Better performance

We didn't set out to make our app faster with Hyperview, but that's exactly what happened. In our older code, screens would sometimes require data from multiple API requests before they could be rendered. With Hyperview, each screen just makes one request, significantly reducing latency. Additionally, the Hyperview response contains only the data needed by the screen, unlike generic API requests that may return extraneous data.

Instant updates

When we're ready to ship a feature, we just deploy our backend. Our apps pick up the new HXML content the next time users open the app. If we discover a bug, we just rollback our backend and 0 users remain affected. Hyperview enables Continuous Integration and Continuous Delivery strategies for native mobile apps.

Consistent experience for all of our users

Since our Hyperview screens are always pulling the latest designs from our backend, we can assume that every user sees the same version and feature set. This is incredibly important for efficacy and fairness in a two-sided marketplace like Instawork. With Hyperview, we know that users aren't disadvantaged because they're using an older version of our app.

Get started today

Hyperview is truly the best way to develop networked apps for mobile devices. Based on our experience at Instawork, it takes some time to adjust to a thin-client paradigm when working with mobile apps, but once you get used to it, you will not want to go back! There are many ways to get started:

  • If you want to learn Hyperview by reading example code, head over to the Examples section.

  • To get a full sense of the elements and behaviors available in Hyperview, check out the full HXML Reference.

  • If you're coming from a web/HTML background, you may want to start with our guide highlighting some of the differences between HTML and HXML.

  • Or, to run a demo yourself, check out our Getting started guide on the next page!

Hyperview
Docs
ExamplesHyperview Reference
More
BlogGitHubStar
Copyright © 2023 Instawork