News App, Justify Thyself
How and why we built a Guardian Mobile Innovation Lab app.
A couple of months after the Guardian Mobile Innovation Lab celebrated its first birthday, we launched something new — an iOS app. Now that we’ve had a chance to take it public and send live video notifications on inauguration day, we can take a step back and walk through the hows and whys of what we made.
Why even build an app at all?
Building a native app is a huge endeavour in both time and money, so it’s worth stopping to think about the reasons for making one before you even start. The web, of course, works absolutely fine on mobile devices, and many sites that receive a bulk of their traffic through social media get by well without any kind of app. But apps are very valuable for those who do have them — typically, user engagement is far higher in an app than it is on the web, and breaking news alerts, widgets and other deep OS integration allow for deeper relationships with readers.
For the lab, the decision was a little more straightforward. We were running a series of experiments based around web notifications, which only work on Android phones. It was clear that we were missing a piece of the puzzle by not running these experiments on iOS, and with no sign of web notification support in iOS 10 (which, when it launched in September 2016, had introduced a lot of new notification functionality), we knew we needed to develop an app.
We could have thrown together very basic apps for each experiment, but got to thinking about the app as an experiment itself: If you were to build a news app from scratch in 2016, what would it look like? What would it do? The best answer we could draw from our experience in newsrooms is: anything and everything.
It’s not that we can’t think of any possibilities. If anything, there too many of them. New stories break all the time, and each could have its own presentation: a live blog for this breaking news event, a sequential series of notifications for that one. A pinch-and-zoom map for this set of data, an animated infographic for that one.
So, I started hacking away with one main thought in mind: how flexible can this be?
But wait, what does flexible mean?
You can’t make an app with at least some parameters in mind, so we applied a little newsroom experience to draw up a wish list for this app.
Presentation should work cross-platform with as little work as possible.
It should be possible to create a custom presentation (be it text, video, interactive, or a mix of everything) and have it work on all platforms with as little custom code as possible. Newsroom developers already face frequent and significant time crunches on projects, so the more work put on them for device support, the more chance things will fall through the cracks.
Those playing along at home might have already worked out what that means: we’re going to be using webviews to render content. “But the web was designed for documents, not apps!”, I hear you cry. And that’s true. But news apps differ from typical native apps in an important way — their primary role is as a document viewer. Once you factor that in, sprinkling UI on top of a rich document layout engine makes a lot more sense than sprinkling document functionality on top of a UI layout engine.
Our presentation method should dictate as little as possible.
It should be modular, and update on the fly.
All app developers struggle with Apple’s requirement that it review every app update before it is released to the public in the App Store, but news app developers probably struggle more than most. Any event-based feature has a hard deadline you’re not going to be able to renegotiate, so if your app update with a new Oscars feature is still stuck in review when Oscars night comes around, then tough luck. If you discover a bug in that feature after the night begins? Even tougher luck. Ideally the app should be able to download new functionality without requiring an App Store update — something unthinkable a few years ago, but that React Native can already do.
On a related note, it would be great to release that Oscars feature without having to worry about bundling it together into one big app release. This is no small feat, as it means we’ll need to be able to separate different sections of the app from each other almost entirely.
Our modular, webview-based presentation should still be able to access native functionality.
Plenty of apps already render content in a webview, but the content is locked out of all native functionality, such as social media sharing dialogs, notifications, offline content, and even external displays.
This is starting to sound familiar…
The more we sketched the idea out, the more we realised it matched something we already knew: the Service Worker API.
Service Workers are the web technology that allowed us to conduct our web notification experiments, and while we didn’t make use of it at the time, it also contains functionality for offline caching, on-the-fly updates, and modularity (by using multiple workers with different scopes). And it all already exists on Android, leaving iOS as the only unsolved puzzle.
It was pleasantly surprising to see just how much we were able to replicate, while keeping the app feeling as ‘native’ as possible. Our app currently features:
Native UI surrounds
We’re using a UINavigationController and multiple WKWebViews to keep the navigation stack in the app fully native, so that it retains the title controls, back button and back swiping of any other app. It also uses the title and theme-color meta tag to populate the native surrounds automatically.
It also hides the “white flash” of a page loading within the WKWebView by rendering it off-screen and waiting for the view to paint before pushing it into the view stack.
Service Worker registry, lifecycle, and communication with web clients
A webview within the app can register a worker, then use postMessage to send data to the worker, and receive a reply via a MessagePort. The workers themselves go through the same install and activate lifecycle that they do on the web. Service Workers can be updated via navigation event, or by calling the .update() function.
Caching and custom ‘fetch’ event handling
The app is fully offline-capable via the ‘fetch’ event in the Service Worker API. Once a worker has been registered, any subsequent attempts to load content within its scope is redirected to a local GCDWebServer, which passes the request into the service worker and returns its response (note that this has security implications, so don’t do something like this without thinking about it).
Notification API implementation
The app has an implementation of the web notifications API that maps those calls to the iOS native equivalents for local notifications, remote subscriptions and push events. It supports tags, silent notifications, images and action buttons, with a few caveats. We’ve had great success using Firebase Cloud Messaging to send the same notification to web and iOS clients simultaneously.
The iOS 10 Notification Content extensions also allow us to run a worker while an expanded notification is active, to react to button taps etc. without having to open the app itself.
Notification API extensions
In order to take advantage of some iOS 10-specific features, we made some additions to the Notifications API. It has a video attribute allowing us to play a video instead of show an image, as well as a canvas attribute that allows us to use an implementation of the OffscreenCanvas API to create custom animations in an expanded notification, including on top of a video (it’s how we did the overlay on top of our live video notifications).
The code for the app is available online, but it’s not very usable yet. We’re in the process of refactoring it from a curious experiment that grew into a giant blob of code into a sensible, modular app, and improving API compatibility along the way. Our inauguration coverage surfaced a good few bugs we’ll be fixing along the way, and writing up the details of how we implemented some specific features.
We’re hoping that we end up with a fully featured app that others will be able to use simply by changing the name and logo, then instructing it to load a progressive web app you’ve already created.
But in the meantime, we’d like to hear from you. Do you work on content that ends up in an app, or on an app itself? What do you wish worked better?