A proof of concept for a React Native delivery app that will display the status, driver name, and expected delivery time of the order, on the soon to come new iOS 16.1 feature: “Live Activities”.
Live Activities weren’t included in the initial version of iOS 16, but will be available to the public in an update (iOS 16.1) later this year. This new feature will display and update an app’s most current data on the iPhone Lock Screen, which will allow people to see the live information they care about the most at a glance: it will allow users, for example, to keep track of sports’ game scores, food or package deliveries’ current status, and more, right from their Lock Screen.
Same day delivery apps are ubiquitous since the not-so-long-ago lock-downs precipitated by the pandemic. So it comes to no surprise I’ve been working for the past year on a delivery app project!
When Apple announced Live Activities at the WWDC as a way for developers to add real-time, up-to-date information to the iPhone Lock Screen I immediately thought: “Cool, I am in the perfect project to test that!”.
Since we are developing the app in React Native I decided to develop a Proof-of-Concept (POC) experiment to understand how we could integrate this new feature in RN.
Conditions / Constraints
- A Live Activity can only be active for eight hours, after that the system will automatically end it. After the end, the Live Activity remains on the Lock screen for up to four hours before the system removes it.
- Each Live Activity runs in its own sandbox, and can’t access the network or receive location updates.
- The updated dynamic data for ActivityKit or remote push notifications can’t exceed 4KB.
Since Live Activities must work on top of the ActivityKit how can we implement it in a React Native App?
This API owns the responsibility of handling the life cycle of each Live Activity so we need to use it to request, update and end the Live Activity.
Live Activities receive updated data from your app with ActivityKit or by receiving remote push notifications. — Apple
Implementing Live Activities on React Native
The POC’s main goal is to create a React Native app that is able to control Live Activities. The app should be able to:
- Start a Live Activity
- Define the content of that Live Activity
- Stop the Live Activity
- Update the Live Activity
- List all Live Activities
To enable React Native to use the WidgetKit’s functions and methods I created a native module using Create React Native Library which will create a native module and a React Native app (see here on how to create a RN native module).
We can start by implementing a struct for the Live Activity state:
struct MyActivityAttributes: ActivityAttributes { public struct ContentState: Codable, Hashable { var status: String var driverName: String var expectedDeliveryTime: String }}
Then we need to build 4 functions on Swift or Objective-C:
- start Live Activity
- update Live Activity
- end Live Activity
- list all active Live Activity
These functions will control and manage Live Activities by interacting with the ActivityKit. The example below will start a new Live Activity:
@objc(startActivity:withDriverName:withExpectingDeliveryTime:withResolver:withRejecter:)func startActivity(status: String, driverName: String, expectingDeliveryTime: String, resolve:RCTPromiseResolveBlock,reject:RCTPromiseRejectBlock) -> Void {
if #available(iOS 16.1, *) { var activity: Activity<MyActivityAttributes>? let initialContentState = MyActivityAttributes .ContentState(status: status, driverName: driverName, expectedDeliveryTime: expectingDeliveryTime ) let activityAttributes = MyActivityAttributes() do { activity = try Activity.request(attributes: activityAttributes, contentState: initialContentState) resolve("Requested Live Activity \(String(describing: activity?.id)).") } catch (let error) { reject("Error requesting Live Activity \(error.localizedDescription).", "", error) } } else { reject("Not available", "", NSError()) }}
After that, we need to expose these functions to the Native Module.
RCT_EXTERN_METHOD(startActivity:(NSString)status withDriverName:(NSString)driverName withExpectingDeliveryTime:(NSString)expectingDeliveryTime withResolver:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject);
RCT_EXTERN_METHOD(listAllActivities:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject);
RCT_EXTERN_METHOD(endActivity:(NSString)status withResolver:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject);
RCT_EXTERN_METHOD(updateActivity:(NSString)id withStatus:(NSString)status withDriverName:(NSString)driverName withExpectingDeliveryTime:(NSString)expectingDeliveryTime withResolver:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject);
And so the native part of the module is done.
Now, in React Native, let’s create the 4 functions in JavaScript:
function startActivity( status: string, driverName: string, expectingDeliveryTime: string);
function listAllActivities();
function endActivity(id: string);
function updateActivity( id: string, status: string, driverName: string, expectingDeliveryTime: string);
After installing the module in React Native, we then create the Live Activity itself on the iOS project.
For that let’s create a widget extension (documentation here) on Xcode and also add
as
to the
of the project.
Then we can create the Live Activity UI in SwiftUI using the struct created on the Native Module:
@main@available(iOS 16.1, *)struct LiveActivityDynamicIsland: Widget { var body: some WidgetConfiguration { ActivityConfiguration(for: MyActivityAttributes.self) { context in HStack { Image(systemName: "bicycle") .foregroundColor(.blue) .padding(8) VStack(alignment: .leading) { Text(context.state.status) .font(.body) Text(context.state.driverName) } Spacer() VStack(alignment: .trailing) { Text("Deliver at") .font(.body) Text(context.state.expectedDeliveryTime) .font(.footnote) } .padding(8) } .padding(8) } dynamicIsland: { context in DynamicIsland { DynamicIslandExpandedRegion(.leading) { Image(systemName: "bicycle") .foregroundColor(.blue) .padding(8) } DynamicIslandExpandedRegion(.trailing) { VStack(alignment: .leading) { Text("Deliver at") .font(.body) Text(context.state.expectedDeliveryTime) .font(.footnote) } } DynamicIslandExpandedRegion(.center) { Text(context.state.status) .font(.body) } DynamicIslandExpandedRegion(.bottom) { Text(context.state.driverName) } } compactLeading: { Image(systemName: "bicycle") .foregroundColor(.blue) } compactTrailing: { Text(context.state.expectedDeliveryTime) } minimal: { Text(context.state.expectedDeliveryTime) } .keylineTint(.yellow) } }
Finally, in the React Native project, create an activity:
import { startActivity } from 'react-native-live-activity';await startActivity("Packing", "Jhon", "12 PM")
All the code for this POC here.
Video showing an example of a React Native app using Live Activities. You can download it here.
Conclusion #Live Activities on React Native
Although we need to create a native module to give RN access to the ActivityKit’s functions and aside from some simple Swift coding for the Live Activities UI, it is possible and fairly straightforward to create a stable RN app that can use and manage the activities from within React Native.