React Native App with Apple Watch & Widget Support

Extending your app’s reachability to your users’ wrists

Kunjal Soni
Simform Engineering
9 min readJun 27, 2024

--

We all love developing mobile apps using React Native because it provides cross-platform integration which is widely used for iOS as well as Android applications. But do you know what is more exciting?

You guessed it right! It is a React Native application which also supports smart watch devices.

In this article, we will learn how to integrate an Apple watchOS application with our React Native application. Moreover, we will also create a widget which can be set as a complication in watch faces.

Prerequisites

  • Basic knowledge of React Native, Xcode, and SwiftUI
  • Xcode with watchOS-supported device simulator

Setting up your development environment

To build a watchOS app for our React Native app, we will use Xcode, which is an IDE provided by Apple.

Moreover, we will use WatchKit, which is a framework provided by Apple to build watchOS apps. With WatchKit, we can create watchOS apps and program them to connect and work with apps on other Apple devices.

Installing necessary dependencies for watchOS and widgets

To implement communication between iOS and watchOS apps, we will use a library named react-native-watch-connectivity.

  • To install this package, you may use yarn or npm based on your project:
npm install react-native-watch-connectivity - save
yarn add react-native-watch-connectivity

Don’t forget to install cocoapods!

cd ios && pod install && cd ..

Next up, we will create a very basic React Native screen which will have a count variable with plus and minus buttons.

Adding support for watchOS app

Now that you have set up your iOS app, let’s move forward and start developing our watchOS app.

Open your iOS project using Xcode. From the toolbar, select File -> New -> Target.

You will see the following window: From the top tabs, select watchOS, and under that tab, select App.

Adding watchOS app to your project

Next up, you will see another window to provide more details about the watchOS app.

Provide an app name and bundle identifier for the watchOS app.

Provide additional information for the watchOS app

From the window shown above, you can select whether you already have an existing iOS app or not. In our case, we have an iOS app project setup. So we will select Watch App for Existing iOS App, and from the drop-down below, select our iOS app’s target.

After clicking the Finish button, you will notice that a new folder has been added to our Xcode project. That folder contains our watchOS project files.

Designing the watchOS app’s UI

Now, we will start designing the UI for our watchOS application. To begin, open the ContentView.swift file, which contains SwiftUI code.

In our SwiftUI code, we will display a simple text element that will show the value of the counter.

Our ContentView should look like this:

Implementing functionality

Now, let’s set up a mechanism for two-way communication between the iOS and the watchOS apps.

Sending messages from the iOS app to the watchOS app

  • iOS app as sender

To send messages to the watchOS app, we will need to use the package we have installed in the React Native app.

Let’s import sendMessage and getReachability function from the react-native-watch-connectivity and use them to determine the watch’s reachability to send the count variable to the watchOS app.

We will modify our button actions as shown below:

  • watchOS app as receiver

To set up the watchOS app to receive messages sent from the iOS app, let’s create a new Swift file and name it ConnectionHelper.swift. We will use this class to capture messages and events sent from the iOS app.

To begin, let’s create a class named ConnectionHelper and inside that, we will set up our watch session and events.

This code will give you an error, which forces us to implement the required methods in our class for the watch session delegate.

To keep the code clean, create an extension of our class ConnectionHelper and implement WCSessionDelegate into it. The extension must also implement the method shown below, as it is a required method.

Info: An extension in Swift allows you to add new functionality to an existing class.

This method is useful for indicating when there are any changes in the activation state of WCSession. It means we can see that communication between your iOS app and the paired Apple watch is now active. If not, it sends an error.

The next step is getting events and messages that are sent from our iOS app into our watchOS app, for which we will implement the didReceiveMessage method in the extension. Our ConnectionHelper extension should look like this now:

Using the above-declared method, we will update the value of our counter. Let’s first declare it inside our ConnectionHelper class. Our class should now look as follows:

We declared a variable count, annotated it as Published and implemented ObservableObject protocol in our class because we want to track whenever this count variable is changed.

Now, we need to implement a logic that updates the count value whenever we receive an event from the iOS app. To achieve this, we will use the didReceiveMessage method defined in our ConnectionHelper extension.

Info: Whenever you get a message into the watchOS app from iOS app, you can send a reply by using the replyHandler callback.

Now that we have created our counter variable, it’s time to use it inside our watchOS app’s UI. Let’s instantiate the ConnectionHelper class in our ContentView and use the count property to display it under the text element.

Now, our one-way communication from the iOS app to the watchOS app is complete.

Sending messages from the watchOS app to the iOS app

  • watchOS app as sender

To implement sending messages from the watchOS app to the iOS app, we will first create a helper function inside the ConnectionHelper class that will send the count variable as a message from the watchOS to our iOS app using the watch session that we previously created.

In the next step, we will modify our ContentView file to add buttons with text + and - followed by adding actions to them. As shown in the code snippet below, we will modify the count variable and use the sendNewCount function to send it to the iOS app.

But wait! We still need to set up our iOS app to listen to the messages sent from the watchOS app.

  • iOS app as receiver

To handle these incoming messages, we first need to use watchEvents to listen to the messages, which are imported from the react-native-watch-connectivity package.

Voila! That’s it. Let’s run both apps and give them a try.

Communication between iOS and watchOS apps

Adding widgets to the watchOS application

Setting up the Widget Extension target

To introduce widget support for our watchOS app, we will need to create a new target in the same Xcode project.

We will add a new target named Widget Extension to our project, similar to the watchOS target.

Adding Widget Extension target

After clicking Next, provide the product name for your widget target.

Providing details of Widget Extension target

Once you have set up your Widget Extension target, you will see a new folder in your project, named CounterWidget in this case. This folder will initially contain two Swift files, AppIntent and CounterWidget.

Designing and implementing the widget UI and its functionality

Before modifying the UI, let’s first set up our count variable in the Widget Extension.

To accomplish this, open the AppIntent file, declare a variable named currentCount, and set up the constructors as shown in the code snippet below:

To start designing our widget’s UI, let’s open the CounterWidget file. Scroll down to the EntryView, which was automatically generated by Xcode when we created the Widget Extension. We will create a very simple text component and, inside it, show our current count variable as shown below:

Communicate and share data between the watchOS app and the Widget Extension

Now, we want to show our current count variable, declared in the watchOS app, in our widget’s UI. But Apple does not provide a direct way to exchange data between the watchOS app and a widget. So, we will store our count variable in UserDefaults, which is local storage provided by Apple.

More info on UserDefaults: https://developer.apple.com/documentation/foundation/userdefaults

To share a single property across our watchOS app and Widget Extension, we have to add a new capability called App Groups for both watchOS and Widget Extension.

To do so, open Project Settings -> select your target, and then select the tab Signing & Capabilities. Click the + Capability button to your target in that window.

Adding capability

After adding App Groups as capabilities, you will see this section inside that window. Now, tap the + button and add an identifier.

Adding App Groups

Now, provide a valid identifier for the container (which will also be used as a suite name for UserDefaults) where our data will be stored and used by multiple targets in our project.

Provide app group container name

Note: Make sure to do this step for both the watchOS app and the Widget Extension.

Adding App Groups capability to the watchOS and Widget Extension targets

The CounterWidget file will contain all the logic that manages the user interface (UI) and its functionality. However, the main function is the timeline function, which is responsible for refreshing the widget’s UI at specified intervals.

For more info about it, read: https://developer.apple.com/documentation/widgetkit/appintenttimelineprovider

Moving forward, now that we have our app groups ready, let’s set up the mechanism for sharing our data between the watchOS app and the Widget Extension.

We will declare a new variable called appCount inside our ConnectionHelper class, which is declared in the watchOS target’s folder, and it will be annotated with AppStorage using UserDefaults, as shown below:

In addition to that, to make the appCount variable functional, we will assign a new value to it whenever the count is changed.

In the next step, to display it inside the widget’s UI, we want to modify the Timeline Provider of the widget. For that, navigate to the pre-generated Provider structure inside the CounterWidget file.

First, we want to declare the appCount variable in that structure’s scope and modify the provider functions so that they look as shown below:

Hallelujah! We have successfully set up our watchOS app’s Widget Extension to show the count value. Now let’s run and give it a shot.

Updating Widget’s UI when count is changed from the watchOS app

Let’s try modifying the count from the iOS application.

Updating Widget’s UI when count is changed from the iOS or watchOS app

The full project is available on this GitHub repo. 🚀

Conclusion

We successfully integrated the React Native app with watchOS, as well as widget support for the watch faces.

Now, it’s your turn to create awesome apps using WatchKit and WidgetKit. Share your ideas and projects in the comments below.

Happy coding! 😉

For more updates on the latest tools and technologies, follow the Simform Engineering blog.

Follow us: Twitter | LinkedIn

--

--