flutter_uwb 1.0.1 copy "flutter_uwb: ^1.0.1" to clipboard
flutter_uwb: ^1.0.1 copied to clipboard

Ultra-wideband proximity for Flutter — cm-level distance, real-time ranging, Precision Find.

flutter_uwb — Ultra-wideband proximity for Flutter

flutter_uwb pub.dev pub.dev points platforms license

Features #

  • Distance + direction — centimeter-level distance on every UWB device, plus azimuth/elevation where the hardware supports it.
  • One Dart API, two platforms — the same streams and method names on Android (androidx.core.uwb) and iOS (NearbyInteraction).
  • Discovery + pairing built in — peers find each other over BLE / MultipeerConnectivity and the plugin runs the full UWB token exchange end-to-end. No QR codes, no hand-rolled signalling.
  • Accept or decline incoming requests — a peer's pairing request surfaces on the incomingRequests stream before UWB starts, so your app decides whether to pair. Works on both platforms.
  • Apple FiRa accessories (iOS) — range against Qorvo, NXP, and any certified FiRa tag via the Apple NI Accessory Protocol; register a vendor's BLE service UUIDs with one call.
  • Encrypted Android↔Android — per-session X25519 ECDH, an HKDF-SHA256 session key, and an HMAC-authenticated token exchange over BLE. iOS rides Apple's protected discovery channels.
  • Camera-assisted ranging (iOS) — opt into ARKit-backed directional ranging on supported hardware.
  • Lifecycle-safe sessions — NI sessions tear down automatically on background/terminate; no ARKit stalls, no crashes on resume.
  • Production-readycheckReadiness() and getDeviceCapabilities() surface support and permissions before ranging, typed UwbErrorCodes explain failures, and everything — discovery, samples, errors, lifecycle — arrives as Streams.

What ranges against what #

Pair Status
iPhone ↔ iPhone ✅ Stable
Android ↔ Android ✅ Stable
iPhone ↔ Apple-FiRa accessory (Qorvo, NXP, MFi tag) ✅ Stable
iPhone ↔ Android (cross-OS) ❌ Not supported (see Architecture)

Platform support #

Platform Minimum hardware Notes
Android Pixel 6 Pro+, Galaxy S21 Ultra+, or any device exposing FEATURE_UWB Android 14+ recommended for stable UwbAvailabilityCallback behavior.
iOS iPhone with U1/U2 chip (iPhone 11+, excluding SE 2/3) on iOS 16+ Camera assist & extended distance gated by RangingOptions. iOS 14/15 hosts must pin to 0.3.1.

isUwbAvailable() returns false on emulators, the iOS simulator, and devices without a UWB chip — always check it before calling discovery.

iOS 26 / U2 chip caveat. Apple disabled supportsDirectionMeasurement for the U2 chip on iOS 26, so iPhone 15 Pro / Pro Max and the iPhone 16 series report null for azimuthDegrees and elevationDegrees. Distance is unaffected.

Installation #

flutter pub add flutter_uwb

Requires Flutter >=3.22 and Dart >=3.3.

Quick start #

Both peers run the same code. Pick a unique localName for each side.

import 'package:flutter_uwb/flutter_uwb.dart';

final uwb = FlutterUwb.instance;

if (!await uwb.isUwbAvailable()) return;

// Acceptor side — auto-respond when a peer initiates pairing.
uwb.incomingRequests.listen((req) async {
  final myToken = await uwb.getLocalToken(UwbRole.controlee);
  await uwb.acceptRequest(req.device.id, myToken);
  await uwb.startRanging(req.device.id);
});

uwb.rangingSamples.listen((s) {
  print('${s.distanceMeters.toStringAsFixed(2)} m  '
        '${s.azimuthDegrees?.toStringAsFixed(1)}°');
});

await uwb.startDiscovery('phone-A');

// Initiator side — call from your UI when the user picks a peer.
Future<void> pairAndRange(UwbDevice device) async {
  // Accessories handshake via the Apple NI Accessory Protocol — skip pairWith.
  if (!device.platform.startsWith('accessory')) {
    await uwb.pairWith(device.id);
  }
  await uwb.startRanging(device.id);
}

When you're done:

await uwb.stopRanging();
await uwb.stopDiscovery();

Pairing is asymmetric: one side calls pairWith (the initiator); the other side's incomingRequests stream fires and that side calls acceptRequest. Both sides then call startRanging. Trigger the initiator from your own UI — a button, a QR scan, a server event, whatever fits.

Apple-FiRa accessories (iOS only) #

To range against a Qorvo, NXP, or third-party Apple-FiRa tag, register the vendor's BLE service triplet before startDiscovery:

await uwb.registerAccessoryProfile(
  serviceUuid: '<accessory service UUID>',
  rxUuid:      '<accessory rx UUID>',
  txUuid:      '<accessory tx UUID>',
  vendorTag:   'my-tag', // optional — surfaces as `accessory:my-tag`
);

The accessory shows up in deviceFound with device.platform == 'accessory:my-tag'. The Quick Start's pairAndRange already handles it — the platform check skips pairWith and lets startRanging drive Apple's NI Accessory Protocol. Calling registerAccessoryProfile on Android throws UwbException (iOS-only in 1.0.0).

See example/lib/main.dart for a working Qorvo DWM3001CDK profile.

A complete runnable demo lives in example/.

API #

Stream Fires when
deviceFound A new peer is discovered via BLE / MPC
deviceLost A previously-discovered peer disappears
incomingRequests A peer sends us their UWB token; reply with acceptRequest or declineRequest
rangingSamples A new RangingSample arrives from the active session
peerLost The ranging peer disconnects mid-session
rangingErrors A platform error occurs inside the active session

RangingSample exposes distanceMeters, azimuthDegrees, elevationDegrees, elapsedRealtimeNanos and the originating deviceId. All mutating methods throw UwbException on failure.

startRanging accepts an optional RangingOptions(cameraAssist, extendedDistance) for iOS opt-ins. Use getDeviceCapabilities() to gate the toggles in your UI.

checkReadiness() returns a snapshot of the UWB radio, Bluetooth, and runtime-permission state — use it before startDiscovery / startRanging to drive an onboarding flow without trying to range first and catching the failure.

Full API docs: https://pub.dev/documentation/flutter_uwb/latest/

Permissions #

Android

The plugin manifest already declares the required <uses-permission> entries. Your app only needs to request them at runtime:

API level Runtime permissions
31+ BLUETOOTH_SCAN, BLUETOOTH_ADVERTISE, BLUETOOTH_CONNECT
≤ 30 ACCESS_FINE_LOCATION
33+ additionally UWB_RANGING
import android.Manifest
import android.os.Build
import androidx.core.app.ActivityCompat

private val perms: Array<String> = buildList {
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
    add(Manifest.permission.BLUETOOTH_SCAN)
    add(Manifest.permission.BLUETOOTH_ADVERTISE)
    add(Manifest.permission.BLUETOOTH_CONNECT)
  } else {
    add(Manifest.permission.ACCESS_FINE_LOCATION)
  }
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
    add(Manifest.permission.UWB_RANGING)
  }
}.toTypedArray()

ActivityCompat.requestPermissions(this, perms, /*requestCode*/ 1)
iOS

Add to ios/Runner/Info.plist:

<key>NSNearbyInteractionUsageDescription</key>
<string>Used to measure precise distance to nearby devices over UWB.</string>

<key>NSBluetoothAlwaysUsageDescription</key>
<string>Used to discover nearby devices for UWB ranging.</string>

<!-- Required for iOS↔iOS pairing on iOS 17+ (keeps the AWDL sidechannel alive). -->
<key>NSLocalNetworkUsageDescription</key>
<string>Used to coordinate UWB ranging with nearby iPhones.</string>
<key>NSBonjourServices</key>
<array>
  <string>_flutteruwb-uwb._tcp</string>
  <string>_flutteruwb-uwb._udp</string>
</array>

The Bonjour service names must match exactly. If you only target FiRa accessories, the local-network keys are optional but harmless.

Example app #

A runnable demo lives in example/. It wires up discovery, pairing, and a live distance/azimuth readout for same-OS pairs and (on iOS) Apple-FiRa accessories.

flutter_uwb example app

Troubleshooting #

startDiscovery succeeds on Android but no peers appear

Almost always missing runtime permissions. Android 12+ requires the user to grant BLUETOOTH_SCAN, BLUETOOTH_CONNECT, BLUETOOTH_ADVERTISE and UWB_RANGING at runtime — declaring them in the manifest is not enough. Use uwb.checkReadiness() and request anything in missingPermissions (typically with permission_handler) before calling startDiscovery.

If permissions are granted and you still see nothing, check that Bluetooth is actually powered on (r.bluetoothEnabled) and that the peer is also running 1.0.x — wire-format compatibility with 0.3.x peers ended in 0.4.0.

startDiscovery succeeds on iOS but no peers appear

iOS↔iOS pairing runs over MultipeerConnectivity, which needs the Local Network permission. iOS prompts for it the first time you call startDiscovery; if it was dismissed or denied, discovery silently returns no peers and raises no error. Re-enable it in Settings → your app → Local Network. Also confirm NSLocalNetworkUsageDescription and the NSBonjourServices entries (_flutteruwb-uwb._tcp / _udp) are present and match exactly — see Permissions. Note iOS gives no API to query this state, so checkReadiness() cannot report it.

UwbErrorCode.regionalRestriction on first ranging call

UWB is regulated and the OS disables ranging in some jurisdictions even on hardware that ships with the radio. There's no programmatic recovery — surface a region-restriction notice to the user.

iOS ranging starts then stops "randomly"

If the host app backgrounds during a session, the plugin tears down the active NISession and fires onPeerLost so the app can react (the alternative is undefined behaviour from NISession + ARSession left running across suspension). The host app should re-call startRanging on foreground if it wants ranging back.

If you see this without backgrounding, the most likely cause is camera-assist on a session whose ARSession hasn't received its first frame yet — that surfaces as NIErrorCodeInvalidARConfiguration (-5883). Disable cameraAssist in RangingOptions and retry.

How to enable verbose plugin logs
import 'package:flutter_uwb/flutter_uwb.dart';

void main() {
  if (kDebugMode) UwbLog.setLevel(UwbLogLevel.debug);
  UwbLog.setHandler((level, msg) => debugPrint('[uwb] [$level] $msg'));
  runApp(const MyApp());
}

Native logs: adb logcat | grep flutter_uwb on Android, Xcode console filtered by subsystem flutter_uwb on iOS.

Architecture #

For protocol details, token format, BLE/UWB topology, and the ECDH-keyed Provisioned STS handshake on Android, see doc/architecture.md.

License #

MIT

8
likes
150
points
260
downloads

Documentation

API reference

Publisher

verified publisherahmedhamdan.com

Weekly Downloads

Ultra-wideband proximity for Flutter — cm-level distance, real-time ranging, Precision Find.

Repository (GitHub)
View/report issues
Contributing

Topics

#uwb #proximity #ranging #bluetooth #location

License

MIT (license)

Dependencies

flutter

More

Packages that depend on flutter_uwb

Packages that implement flutter_uwb