Skip to main content

Requirements

The SDK is a native module, so it does not run in Expo Go. A custom dev client or a bare React Native project is required.

Install the package

Then, for iOS:
Autolinking handles native linking on both platforms, so you do not need a pod entry, a post_install hook, a settings.gradle include, or a MainApplication registration. Pre-built native libraries ship in the package for both platforms. pod install should list MeshSdk under “Auto-linking React Native modules”. The iOS native binary ships as an XCFramework, so CocoaPods picks the right slice per build SDK and device and simulator builds both work with no linker configuration.
Upgrading from a version below 0.20.0? Delete these from your ios/Podfile. They now break the build:
  1. Any pod 'MeshSdk', ... line, with or without :modular_headers => true. A surviving line pointing into .../mesh-sdk/ios fails pod install outright with a “no podspec found” error.
  2. Any post_install hook that configured MeshSdk, in particular one setting DEFINES_MODULE, SWIFT_INCLUDE_PATHS, LIBRARY_SEARCH_PATHS, or OTHER_LDFLAGS.
  3. Any flag naming offline_protocol_uniffi_sim or offline_protocol_uniffi_device. Those archives no longer exist and linking them fails with library not found.
Simulator builds work from 0.20.0 onward. If you previously concluded this SDK was device-only, that was a packaging bug, and any workaround you carried must be removed.

iOS setup

Info.plist

For background BLE operation, declare both modes: the SDK creates its central manager with a state-restoration identifier, which CoreBluetooth only honors when bluetooth-central is declared. For the optional Wi-Fi peer-stream transport, also add the keys in phone Wi-Fi peer streams.

Android setup

What the SDK already declares

The SDK ships its own AndroidManifest.xml, and these entries merge into your app automatically, so you do not need to copy them:
The neverForLocation flags on BLUETOOTH_SCAN and NEARBY_WIFI_DEVICES and the maxSdkVersion="30" gating propagate into your merged manifest. neverForLocation has Play Console data-safety implications you inherit, so it is worth knowing it is there. If your manifest declares NEARBY_WIFI_DEVICES without neverForLocation, yours wins the merge and Wi-Fi Direct needs a location grant on Android 13 and later.

What your app must declare

The SDK deliberately does not force these on consumers, but the code requires them:
POST_NOTIFICATIONS is easy to miss. start() unconditionally launches a foreground service that posts a “Mesh Active” notification with a Stop action. On Android 13+ that notification is silently suppressed without the runtime grant, so users get no way to see or stop the mesh.

ProGuard / R8

Nothing to configure. The SDK ships consumerProguardFiles, so its keep rules are applied to your release build automatically. Without them, release builds would silently lose the BLE mesh transport while debug builds kept working, but the rules are already handled for you.

Runtime permissions

Android

The SDK checks permissions but never requests them. Your app must request them before calling start(), or the SDK emits a diagnostic event at error level with “Missing Bluetooth permissions”.
BLUETOOTH and BLUETOOTH_ADMIN are install-time permissions on API 30 and below, so they do not need a runtime request. You can also check and prompt for Bluetooth itself:

iOS

No runtime API call is needed. The system prompts on first CoreBluetooth use, driven by the Info.plist usage descriptions.

Phone Wi-Fi peer streams

The wifiDirect transport is optional and off by default. From v0.28.0 it carries traffic on both platforms: each side proves its address with an identity preamble before anything reaches the protocol. wifiDirect: { enabled: true } starts it from start(). An iPhone and an Android phone meet over this slot only on a shared Wi-Fi network that allows multicast and does not isolate clients; an iPhone cannot join a Wi-Fi Direct group. With no shared network they meet over Bluetooth LE or the relay. A Wi-Fi network with no internet counts. An Android phone on 0.28 or earlier does not advertise on the LAN. An iPhone on 0.28.0 or later does not see one on 0.27 or earlier over this slot, because 0.27 used MultipeerConnectivity. See upgrade from v0.27.

Android local network permission

The Wi-Fi network path needs no runtime grant while your app targets Android 16 (API 36) or lower, so it runs on a phone that has not granted NEARBY_WIFI_DEVICES; Wi-Fi Direct then stays off until enableTransport('wifiDirect') after the grant. When the app targets Android 17 (API 37), declare ACCESS_LOCAL_NETWORK and request it before start(), or the LAN carrier stays off with a warning diagnostic. The SDK does not declare it, because a declaration revokes the grant that apps targeting 36 and lower hold by default.
With the slot enabled, every device on the Wi-Fi network can see the phone’s address, the same exposure as a Bluetooth LE advertisement. There is no switch for the LAN alone; turn wifiDirect off to stop both.

iOS keys

List the service type and describe local-network use in Info.plist, or iOS local-network privacy blocks discovery. The first start() with the transport enabled shows the Local Network prompt, and a denial is reported as an error diagnostic.

Android group formation

With wifiDirect: { enabled: true, autoAccept: true } on Android 10 and later, devices of the same app find each other over Wi-Fi P2P service discovery and join one group, with no system dialog on either phone. Expect one to two minutes: two phones took 18 to 225 seconds in the SDK’s device tests, and Bluetooth LE carries traffic meanwhile. Groups of three or more devices are untested. Without autoAccept, pair the phones once in the system’s Wi-Fi Direct settings. groupOwnerIntent is deprecated and unused. The group’s name and passphrase are derived from your mesh app identifier, so they keep apps apart but are not a secret; the identity preamble and end-to-end encryption still protect the traffic. Request NEARBY_WIFI_DEVICES at run time on Android 13 and later, and ACCESS_FINE_LOCATION on Android 12 and lower, before start(). An enable refused for a missing grant is logged and not retried; call enableTransport('wifiDirect') after a later grant. On Android 12 and lower, make sure your ACCESS_FINE_LOCATION declaration also covers API 31 and 32, which the BLE-only entry above does not.

Optional: Android mesh wake

This additional native configuration enables recovery after Android kills your process. Add to your app’s AndroidManifest.xml, inside <application>:
Then register the task at module scope in index.js, not inside a component:
mesh-state is application-owned code: it stores the process singleton, saved configuration, durable receive handler and provisioned relay credentials. Implement those functions before opting in. The example shows their required integration points, not a shipped SDK module. Both the manifest opt-in and module-scope registration are required. The application must:
  1. Attach a durable receive handler before start(). Core acknowledgment precedes the event and is not proof of persistence.
  2. Return promptly if an instance already exists; never create a second protocol for the process.
  3. Restore configuration and transports, including relay credentials passed through enableTransport.
  4. Resolve within the task budget (60 seconds by default, configurable from 10 to 300 seconds). Do not run a permanent loop in the task.
Mesh wake requires React Native 0.76.5+ when the New Architecture is enabled. Headless tasks did not work under bridgeless before 0.76 and were patchy until 0.76.5. On RN 0.84 and 0.85 a core bug (fixed in 0.86) can leave the wake service running after the task finishes.

Troubleshooting

Linking error

If you see the “doesn’t seem to be linked” error:
  1. Run pod install (iOS) and rebuild natively. A JS-only reload will not pick up native changes.
  2. Confirm pod install output lists MeshSdk under “Auto-linking React Native modules”.
  3. Run npx react-native config and check platforms.ios.podspecPath ends in MeshSdk.podspec.
  4. Remove any leftover manual pod 'MeshSdk' line; it can shadow the autolinked pod.
  5. Verify you are not using Expo Go.
Undefined symbols, or “building for iOS Simulator, but linking in object file built for iOS”, means a leftover manual post_install hook is still linking archives by hand. Remove it per the upgrade warning above. For upgrades from earlier releases, see Upgrade the SDK.

Next steps

Set up the SDK in your app