Skip to content

Wearables Helpers

Metadata

  • System type: library

System Intent

  • What this is: WearablesHelpers is a stateless utility object (Android: object WearablesHelpers in WearablesHelpers.kt; iOS: class WearablesHelpers in WearablesHelpers.swift) that wraps the Meta Wearables SDK for two purposes: (1) answering whether any device is registered (in the Meta SDK device list), and (2) answering whether any registered device is available (has an active Bluetooth connection). These two questions read from different SDK state sources and must never be conflated.

Mermaid Diagram

flowchart TD
  subgraph "Meta SDK State (Android)"
    devices["Wearables.devices\n(StateFlow of List)\nCleared on unregistration"]
    metadata["Wearables.devicesMetadata\n(FlowMap keyed by device ID)\nPersists on unregistration,\nonly LinkState changes"]
  end

  subgraph "WearablesHelpers API"
    isReg["isAnyDeviceRegisted()\nReads: Wearables.devices.value.size > 0\nAnswers: is any device in the SDK device list?"]
    isAvail["isAnyDeviceAvailable()\nReads: devicesMetadata.any { LinkState.CONNECTED }\nAnswers: is any device actively connected?"]
  end

  subgraph "Callers"
    settings["Settings screen\nregistration toggle UI"]
    picker["Device picker\nrecording-device-preference hydration"]
    captureSession["capture-session.ts\nstartStreamSession()"]
  end

  devices -->|"authoritative for registration"| isReg
  metadata -->|"authoritative for connection state"| isAvail

  isReg --> settings
  isAvail --> picker
  isAvail --> captureSession

Flows

Flow: isAnyDeviceRegisted

  • Core files:
  • Android: main/app/wearables-module/android/src/main/java/expo/modules/wearablesmodule/WearablesHelpers.kt
  • iOS: main/app/wearables-module/ios/WearablesHelpers.swift
  • Test files: none (called by WearablesModule.isAnyDeviceRegisted() which is tested via settings-screen tests)

Types

isAnyDeviceRegisted() -> Boolean
  Synchronous. No arguments.
  Returns true if the Meta SDK device list contains at least one device.
  Returns false on any exception (defensive — Meta SDK state access can throw before configure()).

Paths

path input output path-type notes
isAnyDeviceRegisted.has-devices Wearables.devices.value is non-empty true happy path At least one device has been registered via Meta SDK
isAnyDeviceRegisted.no-devices Wearables.devices.value is empty false happy path SDK device list empty (never registered, or unregistration completed)
isAnyDeviceRegisted.sdk-throws SDK access throws (e.g. before Wearables.configure()) false error Try-catch returns false as safe default

Pseudocode

// Android
isAnyDeviceRegisted():
  try:
    return Wearables.devices.value.size > 0
  catch:
    return false

// iOS (uses registrationState property, not device list directly)
isAnyDeviceRegisted():
  guard sdkConfigured else: return false
  return Wearables.shared.registrationState == .registered

Critical invariant — Android: Wearables.devices is the correct source. Wearables.devicesMetadata MUST NOT be used for registration state. When the Meta SDK processes an unregistration, it clears Wearables.devices but metadata entries persist with an updated LinkState. Checking devicesMetadata.any { != null } would return true for an unregistered device that still has a metadata entry. See bug log: docs/bugs/2026-05-10-glasses-registration-state-mismatch-after-unregister.md.


Flow: isAnyDeviceAvailable

  • Core files:
  • Android: main/app/wearables-module/android/src/main/java/expo/modules/wearablesmodule/WearablesHelpers.kt
  • iOS: main/app/wearables-module/ios/WearablesHelpers.swift

Types

isAnyDeviceAvailable() -> Boolean
  Synchronous. No arguments.
  Returns true if any device metadata entry has LinkState == CONNECTED (Android) or linkState == .connected (iOS).

Paths

path input output path-type notes
isAnyDeviceAvailable.connected Any metadata entry has LinkState.CONNECTED true happy path Glasses are Bluetooth-connected and ready for capture
isAnyDeviceAvailable.not-connected No metadata entry has LinkState.CONNECTED false happy path No active Bluetooth connection

Pseudocode

// Android
isAnyDeviceAvailable():
  return Wearables.devicesMetadata.values.any { flow ->
    flow.value?.linkState == LinkState.CONNECTED
  }

// iOS
isAnyDeviceAvailable():
  guard sdkConfigured else: return false
  for deviceId in Wearables.shared.devices:
    if let device = Wearables.shared.deviceForIdentifier(deviceId):
      if device.linkState == .connected: return true
  return false

Critical invariant: devicesMetadata is the correct source for connection state. It is NOT cleared on unregistration — entries persist with LinkState updated (e.g. to DISCONNECTED or UNKNOWN). This means isAnyDeviceAvailable() correctly returns false after unregistration even though metadata entries still exist, because LinkState will no longer be CONNECTED.


SDK State Source Reference

Question Authoritative Source Notes
Is any device registered? Wearables.devices (Android), Wearables.shared.registrationState (iOS) Cleared/updated to unregistered on startUnregistration()
Is any device connected? Wearables.devicesMetadata (both platforms) Persists on unregistration; LinkState changes to DISCONNECTED/UNKNOWN

Do not use devicesMetadata to answer registration questions. A metadata entry exists as long as the device has ever been paired, regardless of current registration status.

Logs

Source Location
Android Logcat tag WearablesModule (called via WearablesModule.isAnyDeviceRegisted() / isAnyDeviceAvailable())

Deployment

  • Mechanism: local only (compiled into the iOS/Android app as a native module)
  • Deploy command:
    # iOS
    npx expo run:ios
    
    # Android
    npx expo run:android
    
  • Notes: Android isAnyDeviceRegisted() wraps the call in a try-catch because Wearables.devices can throw if the SDK is accessed before Wearables.configure() is called. iOS guards with sdkConfigured flag (set by WearablesHelpers.markConfigured() after SDK initialization) for the same reason. The mock state (mockRegisteredDevices, mockAvailableDevices) on iOS is used only when the MWDAT_SDK_AVAILABLE compile flag is absent (test builds without the SDK).