Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
# react-native-wear-connectivity

Allows you to connect React Native Mobile apps with WearOS.
Allows you to connect React Native Mobile apps with WearOS and Apple Watch.

| Sending Voice Message (enable audio) | Sending Text |
| ----------- | ----------- |
| <video src="https://github.com/user-attachments/assets/6b14686d-6693-4391-9cc2-b7caf07e9533" width="1000" /> | <video src="https://github.com/user-attachments/assets/03477afa-843b-450e-8e15-f8d2e827ce79" width="1000" /> |

**Note**: Refer to [react-native-watch-connectivity][2] for Apple Watch development.
Apple Watch support is provided through [react-native-watch-connectivity][2].
See the [Apple Watch Setup](docs/applewatch-setup.md) guide for details on Xcode
configuration, entitlements, and pairing instructions.

[1]: https://wearos.google.com
[2]: https://github.com/mtford90/react-native-watch-connectivity

# Table of Contents

- [Installation](#installation)
- [Apple Watch Setup](#apple-watch-setup)
- [React Native API Documentation](#react-native-api-documentation)
- [Jetpack Compose API Documentation](#jetpack-compose-api-documentation)
- [How to run the example](#how-to-run-the-example)
Expand Down Expand Up @@ -69,6 +72,11 @@ Add the following entry to your `android/app/src/main/AndroidManifest.xml` (full
</manifest>
```

## Apple Watch Setup

See [docs/applewatch-setup.md](docs/applewatch-setup.md) for instructions on
setting up Xcode, required entitlements, and pairing a watch app.

## React Native API Documentation

The example of implementation available in the [CounterScreen](example/src/CounterScreen/index.android.tsx).
Expand Down
24 changes: 24 additions & 0 deletions docs/applewatch-setup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Apple Watch Setup

This library integrates with [`react-native-watch-connectivity`](https://github.com/mtford90/react-native-watch-connectivity)
to enable communication between an iOS app and its paired Apple Watch.

## Xcode configuration

1. Open the iOS project in Xcode.
2. Select **File > New > Target…** and add a **Watch App for iOS App**.
3. In **Signing & Capabilities** for both the iOS app and the WatchKit extension:
- Add **App Groups** and use the same identifier (e.g. `group.com.example.watch`).
- Enable **Background Modes** and tick **Uses Bluetooth LE accessories**.

## Entitlements

Both the iOS application and the WatchKit extension must include the chosen
`com.apple.security.application-groups` entry in their entitlements files so
that the two apps can communicate.

## Pairing with a watch

1. Pair an Apple Watch with the iPhone or simulator using the **Watch** app.
2. In Xcode choose a run destination that includes both the phone and watch.
3. Build and run; Xcode installs the watch app and establishes the pairing.
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@
"publishConfig": {
"registry": "https://registry.npmjs.org/"
},
"dependencies": {
"react-native-watch-connectivity": "*"
},
"devDependencies": {
"@commitlint/config-conventional": "^17.0.2",
"@react-native/eslint-config": "^0.72.2",
Expand Down
47 changes: 47 additions & 0 deletions src/applewatch/AppleWatchConnector.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import Watch, { watchEvents } from 'react-native-watch-connectivity';
import type {
Payload,
ReplyCallback,
ErrorCallback,
} from '../NativeWearConnectivity';

/**
* Connector responsible for activating the WatchConnectivity session and
* forwarding messages between the iOS app and the paired Apple Watch.
*/
class AppleWatchConnector {
private activated = false;

/**
* Ensures that the underlying WCSession is activated before any
* communication attempts.
*/
activateSession() {
if (this.activated) {
return;
}
try {
Watch.activateSession();
this.activated = true;
} catch (err) {
console.warn('Failed to activate Apple Watch session', err);
}
}

/**
* Sends a message to the paired Apple Watch device.
*/
sendMessage(
message: Payload,
cb?: ReplyCallback,
errCb?: ErrorCallback
) {
this.activateSession();
Watch.sendMessage(message, cb, errCb);
}
}

const appleWatchConnector = new AppleWatchConnector();

export { appleWatchConnector, watchEvents as appleWatchEvents };
export default appleWatchConnector;
26 changes: 26 additions & 0 deletions src/applewatch/react-native-watch-connectivity.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
declare module 'react-native-watch-connectivity' {
import { EmitterSubscription } from 'react-native';

export interface WatchConnectivity {
activateSession(): void;
sendMessage(
message: any,
reply?: (data: any) => void,
error?: (err: any) => void
): void;
}

export const watchEvents: {
addListener: (
event: string,
listener: (...args: any[]) => void
) => EmitterSubscription;
on: (
event: string,
listener: (...args: any[]) => void
) => EmitterSubscription;
};

const Watch: WatchConnectivity;
export default Watch;
}
22 changes: 15 additions & 7 deletions src/messages.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Platform } from 'react-native';
import type { SendMessage, Payload } from './NativeWearConnectivity';
import { WearConnectivity } from './index';
import { LIBRARY_NAME, IOS_NOT_SUPPORTED_WARNING } from './constants';
import { appleWatchConnector } from './applewatch/AppleWatchConnector';

const UNHANDLED_CALLBACK =
'The sendMessage function was called without a callback function. ';
Expand All @@ -17,7 +17,7 @@ const defaultErrCb = (err: string) => {
console.warn(UNHANDLED_CALLBACK + UNHANDLED_CALLBACK_ERROR, err);
};

const sendMessage: SendMessage = (message, cb, errCb) => {
const sendMessageAndroid: SendMessage = (message, cb, errCb) => {
const json: Payload = { ...message, event: 'message' };
const callbackWithDefault = cb ?? defaultReplyCb;
const errCbWithDefault = errCb ?? defaultErrCb;
Expand All @@ -28,12 +28,20 @@ const sendMessage: SendMessage = (message, cb, errCb) => {
);
};

const sendMessageMock: SendMessage = () =>
console.warn(LIBRARY_NAME + 'message' + IOS_NOT_SUPPORTED_WARNING);
const sendMessageIOS: SendMessage = (message, cb, errCb) => {
const json: Payload = { ...message, event: 'message' };
const callbackWithDefault = cb ?? defaultReplyCb;
const errCbWithDefault = errCb ?? defaultErrCb;
return appleWatchConnector.sendMessage(
json,
callbackWithDefault,
errCbWithDefault
);
};

let sendMessageExport: SendMessage = sendMessageMock;
if (Platform.OS !== 'ios') {
sendMessageExport = sendMessage;
let sendMessageExport: SendMessage = sendMessageAndroid;
if (Platform.OS === 'ios') {
sendMessageExport = sendMessageIOS;
}

export { sendMessageExport as sendMessage };
37 changes: 25 additions & 12 deletions src/subscriptions.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { NativeModules, NativeEventEmitter, Platform } from 'react-native';
import type { AddListener, WatchEvents } from './types';
import { LIBRARY_NAME, IOS_NOT_SUPPORTED_WARNING } from './constants';
import { appleWatchEvents } from './applewatch/AppleWatchConnector';

const _addListener: AddListener = (event, cb) => {
const androidAddListener: AddListener = (event, cb) => {
const nativeWatchEventEmitter = new NativeEventEmitter(
NativeModules.AndroidWearCommunication
);
Expand All @@ -21,20 +21,33 @@ const _addListener: AddListener = (event, cb) => {
return () => sub.remove();
};

const _addListenerMock: AddListener = () => {
console.warn(LIBRARY_NAME + 'watchEvents' + IOS_NOT_SUPPORTED_WARNING);
return () => {};
};
const iosAddListener: AddListener = (event, cb) => {
if (!event) {
throw new Error('Must pass event');
}

let watchEvents: WatchEvents = {
addListener: _addListenerMock,
on: _addListenerMock,
switch (event) {
case 'message':
break;
default:
throw new Error(`Unknown watch event "${event}"`);
}

const sub = appleWatchEvents.addListener(event, cb);
Comment on lines +24 to +36

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Activate WCSession before registering iOS listeners

The new iOS code registers event listeners directly via appleWatchEvents.addListener but never activates the underlying WatchConnectivity session. Activation currently only happens inside AppleWatchConnector.sendMessage, so an app that starts by listening for incoming watch messages (without sending anything first) will never activate the session and will miss all events. Call appleWatchConnector.activateSession() before subscribing so inbound watch messages are received even when no outbound message is sent.

Useful? React with 👍 / 👎.

return () => sub.remove();
};

if (Platform.OS !== 'ios') {
let watchEvents: WatchEvents;

if (Platform.OS === 'ios') {
watchEvents = {
addListener: iosAddListener,
on: iosAddListener,
};
} else {
watchEvents = {
addListener: _addListener,
on: _addListener,
addListener: androidAddListener,
on: androidAddListener,
};
}

Expand Down