flutter_uwb 0.4.1
flutter_uwb: ^0.4.1 copied to clipboard
Ultra-wideband proximity for Flutter — cm-level distance, real-time ranging, Precision Find.
Changelog #
0.4.1 #
- Shrink the brand shields-style badge SVG to 137×20 (was 220×32) so
it lines up vertically with the flat-square shields.io badges next
to it on the README hero. Asset content unchanged — only the outer
<svg>width/height; viewBox preserves the layout. No code changes.
0.4.0 #
BREAKING release. Closes the security gap on Android↔Android UWB
ranging, fixes the silently-broken iOS↔Android peer-mode dispatch,
brings the Android stack onto androidx.core.uwb:1.0.0-rc01, and
tightens the Pigeon schema.
Cross-OS (iPhone ↔ Android) is experimental in 0.4.0. BLE discovery, the Apple-FiRa accessory handshake, and Android UWB session activation all complete, but the underlying
androidx.core.uwbAPI rejects the slot duration Apple selects, so stable distance samples are not yet delivered. Same-OS pairs are unaffected.
Added #
- Provisioned STS for Android↔Android peer ranging. Every BLE OOB
exchange now begins with an X25519 ECDH handshake (HKDF-SHA256
derivation of a 16-byte UWB session key + a 16-byte HMAC key). The
derived session key is fed into
RangingParameters.sessionKeyInfoso the UWB radio session is encrypted end-to-end. Token writes are HMAC-authenticated; tampered or replayed tokens are rejected before reaching the UWB stack. OobCapabilitybyte advertised in BLE service-data on Android and inMCNearbyServiceAdvertiser.discoveryInfoon iOS so cross-OS pairs auto-route to accessory mode and same-OS pairs stay on peer mode without guessing. Peers running 0.3.x advertise no byte; on Android scans of the symmetric service the absence is now treated asiosPeer(iOS BLE strips service-data from advertisements, so missing service-data unambiguously means an iOS host).- iOS now publishes the symmetric BLE service via
CBPeripheralManagerand scans for it as a central, mirroring Android's existing GATT setup. An iPhone shows up in an Android device list asaccessory:iosand a Galaxy in an iPhone list asaccessory:androidautomatically — noregisterAccessoryProfileboilerplate required for cross-OS pairs. - The Android GATT server now sniffs the first byte on the symmetric
CHAR_WRITEto dispatch a connecting central into either the Apple-FiRa accessory protocol (Initialize/ConfigureAndStart/Stop) or the ECDH peer-handshake path. Replies route back through the sharedCHAR_NOTIFY. RangingOptionsPigeon struct onstartRanging(cameraAssist,extendedDistance).DeviceCapabilitiesPigeon struct +getDeviceCapabilities()unifyingNIDeviceCapability(iOS) andRangingCapabilities(Android).UwbErrorCodetyped error code returned via the newRangingError(code, message)event.Stream<UwbSessionState>aggregate on the Dart facade.androidx.core.uwb:1.0.0-rc01(was1.0.0-alpha07):UwbManager.isAvailable(),subscribeToUwbAvailability(UwbAvailabilityCallback),RangingResultInitialized/RangingResultFailure(reasonCode).compileSdk/targetSdkbumped to 35.- BLE OOB now requests an ATT MTU of 247 with a chunked-write
fallback (
BleFramer) when the negotiation stays at the default 23-byte window. - Symmetric
incomingRequestson Android — the PigeononIncomingRequestnow fires on both platforms (was iOS only). UwbHostApiImpl.dispose()is wired throughFlutterUwbPlugin.onDetachedFromEngine(Android) anddetachFromEngine(iOS) so hot-restart no longer leaks BLE resources.
Changed (BREAKING) #
- 0.4.0 peers cannot pair with 0.3.x peers over Android↔Android
BLE OOB. The new ECDH+HMAC envelope is incompatible. 0.3.x peers
fail with a
transportErrorevent instead of silently hanging. - iOS↔Android pairs now auto-route to accessory mode. The discovered
UwbDevice.platformisaccessory:ios(Android side) /accessory:android(iOS side) instead of the previous symmetricpeervalue that crashed on token parse. - Pigeon model fields go non-null where guaranteed (
UwbDevice.id,name,platform;RangingSample.deviceId,distanceMeters,elapsedRealtimeNanos). Dart code that treated them as nullable now sees analyzer warnings. void onRangingError(String deviceId, String message)becomesvoid onRangingError(String deviceId, RangingError error)with a typedUwbErrorCode.- iOS minimum bumps to iOS 16.0 (
flutter_uwb.podspec,s.platform). Hosts on iOS 14/15 must pin to 0.3.1.
Notes #
OobHandshake.swiftis published for wire-compatibility but is not on any active code path in 0.4.0 — cross-OS pairs go through Apple's FiRa accessory protocol whose STS material comes fromNISession.- The Apple-FiRa accessory protocol decoder
(
AppleProtocol.parseAppleUWBConfigData) reads the iPhone-emitted 30-byteAppleUWBConfigDatapayload (session id, channel, preamble, 6-byte STS IV, peer short address). The byte layout is pinned by golden fixtures captured from a real iPhone, stored underandroid/src/test/resources/apple_protocol/.
0.3.1 #
Added #
FlutterUwb.incomingRequests— broadcastStream<IncomingRequest>that fires when a peer sends us their token over the OOB transport (peer-initiated pairing). The companion classIncomingRequestcarries the originatingUwbDeviceand the peer's token bytes, ready to feed into a local ranging session viaacceptRequest+startRanging. The example app now auto-accepts incoming requests to demonstrate the symmetric pairing flow.
Fixed #
- iOS↔iOS ranging on iOS 17+ / iOS 26 no longer hangs. The plugin
now uses
MultipeerConnectivityfor the iOS↔iOS out-of-band token exchange, which keeps an AWDL sidechannel alive during ranging. Previously, with the BLE-only OOB transport introduced in 0.2.0,NINearbyPeerConfigurationwould stay innearbyd #lifecycle DISCOVERY activeindefinitely and never produce samples on iOS 17+. iOS↔Android (FiRa accessory) and Android↔Android paths are unchanged. See Apple Developer Forums thread 802204 for context.
iOS host apps must add two Info.plist keys #
Existing apps that integrate flutter_uwb need to add the following to
their Info.plist, otherwise iOS↔iOS discovery silently returns no
peers:
<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 name must match exactly. See the README for context.
Removed (internal) #
- The dormant BLE GATT peer-mode code path on iOS (
CBPeripheralManageradvertise + custom-service GATT server,BleOob.exchange/BleOob.accept/BleOob.decline). It was no longer invoked once iOS↔iOS pairing moved to MultipeerConnectivity, and removing it shrinksBleOob.swiftto its accessory-mode responsibilities only. No public Dart API change.
Unchanged #
- All previously-existing Dart API methods, types, and streams keep
their current signatures.
incomingRequestsis purely additive — apps that don't listen to it behave exactly as before. - Android plugin code and the iOS↔Android (FiRa accessory) / Android↔Android transports.
Hardware verification #
- iPhone 12 (U1, iOS 26.4.1) ↔ iPhone 16 Pro Max (U2, iOS 26.4):
pairing succeeds, distance updates in real time. As documented by
Apple, the U2 chip on iOS 26 reports
nilfor direction, so azimuth/elevation are only available on the U1 side and only when the phones are roughly facing each other.
0.3.0 #
Changed #
- All mutating methods now throw
UwbExceptionon failure instead of returningVoidResult. Affected:startDiscovery,stopDiscovery,acceptRequest,declineRequest,registerAccessoryProfile,unregisterAccessoryProfile,startRanging,stopRanging. getLocalTokenandexchangeTokensnow throwUwbExceptioninstead ofStateErrorfor consistency.VoidResultis no longer exported from the public API.
Added #
pairWith(String deviceId, {UwbRole role})— convenience method that combinesgetLocalToken+exchangeTokensin a single call.
0.2.0+1 #
Cross-platform v2: a single BLE GATT transport on both platforms plus accessory-mode ranging via Apple's FiRa accessory BLE protocol.
Added #
- iOS BLE GATT OOB — drops MultipeerConnectivity in favour of the same custom-service BLE GATT scheme already used on Android, so iOS↔iOS and iOS↔Android peer-mode discovery share one transport.
- Apple FiRa accessory protocol codec — pure Dart at
lib/src/accessory/apple_protocol.dart(sealedAppleAccessoryMessagewithInitialize/ConfigureAndStart/Stop/AccessoryConfigurationData/AccessoryUwbDidStart/AccessoryUwbDidStop). Mirrored in Kotlin atandroid/.../accessory/AppleProtocol.ktso each native side can dispatch on byte 0 without a Pigeon round-trip per BLE notification. IosAccessoryStrategy(iOS 15+) drives the multi-message Apple handshake against an accessory or another phone speaking the same protocol, then runsNINearbyAccessoryConfigurationfor ranging.AndroidControleeStrategylets Android act as the accessory side of the same protocol (against an iPhone host or any Apple-protocol controller); maps the Apple shareable-config bytes intoRangingParametersand runscontroleeSessionScope.prepareSession.registerAccessoryProfile/unregisterAccessoryProfileDart API for declaring an accessory's BLE service + rx/tx characteristics + optional vendor tag. Replaces an internal placeholder UUID set used during early development.UwbDevice.platformtaxonomy extended:"accessory"for built-in Apple-protocol handling and"accessory:<vendorTag>"for registered vendor namespaces.RangingStrategyinterface on both platforms (RangingStrategy.swift/RangingStrategy.kt) plus per-peer-kind concrete strategies (IosPeerStrategy,IosAccessoryStrategy,AndroidPeerStrategy,AndroidControleeStrategy).UwbHostApiImplis now a strategy dispatcher.- 25 Dart unit tests (
test/apple_protocol_test.dart) plus 9 Kotlin unit tests (AppleProtocolTest.kt) for the codec, including randomised round-trip and malformed-input coverage.
Changed #
- iOS plugin no longer links
MultipeerConnectivity.Info.plistdropsNSLocalNetworkUsageDescriptionandNSBonjourServices. - iOS accessory-mode ranging needs iOS 15+; peer-mode still works on iOS 14+.
Removed #
MultipeerConnectivitydependency on iOS.- The pre-existing scaffold Android test (
FlutterUwbPluginTest.kt) with broken imports was deleted during the strategy refactor.
Hardware verification gaps (called out so future maintainers can #
plan around them)
- iPhone↔iPhone BLE pairing: code complete + sim runtime smoke OK; not yet exercised on a real two-iPhone setup.
- iPhone↔Android cross-platform: BLE handshake and Android UWB session
activation work end-to-end; stable distance samples are blocked by
androidx.core.uwbslot-duration constraints (see the experimental note above). - Android↔accessory: needs a FiRa-compliant accessory with a known service UUID set.
0.1.0 #
Initial public release. Same-platform UWB ranging only (Android↔Android, iOS↔iOS). Cross-platform iOS↔Android is tracked for v2.
Android #
- Jetpack UWB ranging (
androidx.core.uwb:1.0.0-alpha07) viacontrollerSessionScope/controleeSessionScopeand a coroutineprepareSession(...).collect { ... }pipeline. Samples surface to Dart viaUwbFlutterApi.onRangingSample. - BLE GATT-based OOB transport for discovery and token exchange. Custom
service UUID
4f1a9a1c-08d8-4b2e-bc6b-6b1d9f8d7b21. - Manifest declares the full BLE/UWB permission set, branched by API level
(legacy
BLUETOOTH/ACCESS_FINE_LOCATIONfor API ≤ 30, runtimeBLUETOOTH_SCAN/ADVERTISE/CONNECTfor API ≥ 31,UWB_RANGINGfor API ≥ 33). - API-33 4-arg
notifyCharacteristicChangedform on Android 13+, falling back to the deprecated form on older devices. - Multi-peer-aware GATT server (no single-slot pending-central race).
- Emulator detection —
isUwbAvailable()returnsfalseon Android emulators that fakeFEATURE_UWB.
iOS #
NearbyInteraction(NISession) ranging withNINearbyPeerConfiguration. Samples surface via the sameUwbFlutterApi.onRangingSamplechannel.MultipeerConnectivityfor OOB device discovery andNIDiscoveryTokenexchange. Service typeuwb-flutter.- Simulator guard —
isUwbAvailable()returnsfalseon the iOS simulator (NI is hardware-only). - Podspec set to
s.platform = :ios, '14.0'and linksNearbyInteraction/CoreBluetooth/MultipeerConnectivityframeworks.
Dart #
- Public facade
FlutterUwb.instanceexposing future-returning methods and broadcast streams (deviceFound,deviceLost,rangingSamples,peerLost,rangingErrors). - Pigeon-typed schema with
@asynclong-running operations and a@FlutterApievent channel. - Top-level convenience functions retained as a thin compat layer.
Schema #
UwbHostApi— 11 methods, 4 of them@async.UwbFlutterApi— 5 callbacks (onDeviceFound,onDeviceLost,onRangingSample,onPeerLost,onRangingError).
Known limitations #
- Cross-platform iOS↔Android UWB pairing is not supported. Tokens are
opaque platform-specific bytes (Android: 9-byte little-endian blob;
iOS:
NSKeyedArchiverofNIDiscoveryToken). - The example app does not request runtime BLE permissions; the host app
must do that before calling
startDiscoveryon Android 12+.