Implementing Beacon on Apple Watch

Kevin Bunarjo
strava-engineering
Published in
6 min readSep 17, 2019

Beacon — a Summit feature that allows athletes to send a live update of their location to safety contacts.

Back in late April, I started implementing Beacon on the Apple Watch. Aside from UI work, adding Beacon support to our Watch app consisted of two parts. The first part was network requests to transmit the status of the user’s Beacon while the second part was the phone and watch connectivity to put usage of Beacon behind a paywall.

Part 1: Sticks and Stones May Break My Bones, but Producers, Consumers, Filters, Transformers, and Chains Excite Me.

Strava’s watchOS app takes a different approach from the iOS app to processing values over time for recording. It uses chains made up of building blocks called consumers, producers, filters, and transformers.

Producer — the building block that outputs a sequence of values over time. The only restriction of this block is that the data structure of these values stays the same.

Fig 1. Producer Block

Consumer — the building block that takes in a sequence of values over time. Consumers are linked up to producers and process the data they output.

Fig 2. Consumer Block

Filter — the building block that is both a consumer and a producer where the input and output is of the same type.

Fig 3. Filter Block

Transformer — the building block that is both a consumer and a producer where the input and output are different types.

Fig 4. Transformer Block

Chain — a series of consumers, producers, filters, and/or transformers that end up with a desired output value.

Fig. 5: A simple Chain consisting of a Producer, Filter, and Consumer.

Structuring the data flow using these building blocks means that each block in a chain has a specific role and thus makes modularization a natural outcome. Super exciting stuff.

My first main task was to implement the Beacon Emitter consumer that makes network requests to our API. This module handles the logic between state changes for the Beacon during a live recording. The Beacon Emitter consumes the output of the activity state chain, the refresh producer, the Beacon filter chain, the location producer, and the beacon toggle producer. The purpose of each of these inputs is described below.

Activity State Chain

In order to know when to start or end a Beacon, the emitter consumes the product of the activity state chain. The activity state transformer allows the user’s ongoing activity to be stopped, recording, or paused. In the Beacon Emitter, when the user has Beacon enabled and the state goes from stopped to recording, we make a network request to tell our server to create a new Beacon for the user. The server automatically handles the creation of a Beacon link and shares the link with the user’s safety contacts.

Refresh Producer

After the Beacon has been created, we send an update about the athlete’s status on a specified interval. In order to do this, the Beacon Emitter consumes refresh events which are triggered by a timer every second. Once we have a certain number of refresh events with an active Beacon, the emitter will fire off a network request to the server with a summary of the athlete’s activity status.

Beacon Filter Chain

The Beacon Emitter also consumes the product of the Beacon filter chain. This filter chain outputs all the activity information the server needs to update the user’s Beacon — information like the user’s total moving time and total distance, among other things.

This filter chain will continually update the Beacon Emitter even if the user has not enabled Beacon. We store this data structure in the emitter and use the latest version to make the network request.

Location Producer

The Beacon Emitter is also the consumer of a location filter block. This block filters GPS data to present the most ideal location data. All locations are cached since the server updates happen less frequently than the consumption of locations. The transmitted locations are purged from the cache upon each successful transmission.

Beacon Toggle Manager

What if the user starts an activity but hasn’t enabled Beacon? In order to allow users to enable Beacon mid-activity, the Beacon Emitter also consumes the output of the Beacon toggle manager. This Beacon toggle manager is responsible for producing a boolean value whenever the user toggles a switch on or off.

When the user toggles Beacon on in the middle of an activity, we create a new Beacon in the Beacon Emitter. Likewise, when the user toggles it off mid-activity, we tell the server that the user has discarded the current Beacon.

Part 2: A.W. Phone Home — Apple Watch & iPhone Connectivity

Once I got the network requests working, I was able to get started on more Watch-specific tasks. Since Beacon is a Summit feature, we need to get information about whether or not the athlete has access to the Summit Safety Pack. We already keep an up-to-date reference of this information in the iOS app, so we decided to send their subscription status through the phone and watch connection instead of duplicating this infrastructure in the watchOS codebase.

To accomplish this, I used the two-way communication framework (WatchConnectivity) provided by Apple between iOS and watchOS devices. The Strava watch app already uses WatchConnectivity to allow users to easily perform any setup (like logging in) on the iPhone app instead of on the small Apple Watch screen. Since we block the user from performing any actions on the Watch until they have completed setup on the phone, we can always ensure that the Watch receives everything it needs before continuing.

However, managing the state of information sent after setup proved complex. There is no guarantee that the user has opened Strava on their iPhone before they want to use Beacon on the Watch. This means that the user’s access level was not sent from iOS to watchOS previously. We made the product decision that requiring the user to open the Strava app on their iOS device before using Beacon (as we had done before with watchOS onboarding) wasn’t acceptable because of the feature’s primary focus of safety.

To solve this problem, the user’s access level is stored in the Watch’s system for storing user defaults (NSUserDefaults) whenever the iPhone sends it over. If the Watch does not have an access level stored on startup, the Watch will request the current access level from the iPhone using the previously mentioned WatchConnectivity framework, which allows us to access and send the information from the phone in the background. The Watch will continue to communicate with the phone to ensure this value stays in sync.

Conclusion

Working on this entire project was fun yet challenging. By using producers and consumers to modularize our logic, we were easily able to unit test different parts of the Beacon pipeline. Checking the athlete’s Summit status showed that communication between iOS and watchOS devices proved to be more difficult than anticipated when adding new Strava features onto the Apple Watch. However, by investing the time to overcome this hurdle, Strava has made a core feature more accessible, allowing even more athletes to stay safe as they continue exploring new routes. Beacon on Apple Watch is now available to use on the latest version of Strava in the App Store.

Thank you Matt Robinson and Jason Cheng for helping me get this project to the finish line and also Melissa Huang for continually pushing me to strive! Kudos!

--

--