Setup

  • Nearby Connections enables communication between nearby devices using Bluetooth, Wi-Fi, and peer-to-peer connections.

  • Short messages (under 131 bytes) can be sent without establishing a connection, ideal for lightweight data exchange.

  • Long messages (over 131 bytes) require a connection for reliable transfer, supporting various data types and sizes.

  • Both publishers and subscribers need a shared service ID for successful discovery and connection establishment.

  • Connection lifecycle includes advertising, discovery, connection request, data transfer, and disconnection states.

Please refer to Nearby Connections about how to setup Nearby Connections.

Short Messages

Nearby Connections allows its clients to send data within 131 bytes without establishing a connection to the remote device. Please refer to the flow below as how to transfer messages within 131 bytes.

Publisher

class Publisher {
  let connectionManager: ConnectionManager
  let advertiser: Advertiser

  init() {
    connectionManager = ConnectionManager(serviceID: <SERVICE_ID>, strategy: .pointToPoint)
    advertiser = Advertiser(connectionManager: connectionManager)
  }

  func startPublishing() {
    advertiser.startAdvertising(using: <MESSAGE_DATA>)
  }

  func stopPublishing() {
    advertiser.stopAdvertising()
  }
}

Subscriber

class Subscriber {
  let connectionManager: ConnectionManager
  let discoverer: Discoverer

  init() {
    connectionManager = ConnectionManager(serviceID: <SERVICE_ID>, strategy: .pointToPoint)

    discoverer = Discoverer(connectionManager: connectionManager)
    discoverer.delegate = self
  }

  func startSubscription() {
    discoverer.startDiscovery()
  }

  func stopSubscription() {
    discoverer.stopDiscovery()
  }
}

extension Subscriber: DiscovererDelegate {
  func discoverer(_ discoverer: Discoverer, didFind endpointID: EndpointID, with context: Data) {
    // A remote advertising endpoint is found.
    print(context)  // `context` is the published message data.
  }

  func discoverer(_ discoverer: Discoverer, didLose endpointID: EndpointID) {
    // A previously discovered endpoint has gone away.
  }
}

Long Messages

For messages which are larger than 131 bytes, a connection between the devices needs to be established to transfer the message. Please refer to the flow below as how to transfer large data using Nearby Connections.

Publisher

class Publisher {
  let connectionManager: ConnectionManager
  let advertiser: Advertiser

  init() {
    connectionManager = ConnectionManager(serviceID: <SERVICE_ID>, strategy: .pointToPoint)
    connectionManager.delegate = self

    advertiser = Advertiser(connectionManager: connectionManager)
    advertiser.delegate = self
  }

  func startPublishing() {
    advertiser.startAdvertising(using: <ENDPOINT_INFO>)
  }

  func stopPublishing() {
    advertiser.stopAdvertising()
  }
}

extension Publisher: ConnectionManagerDelegate {
  func connectionManager(
    _ connectionManager: ConnectionManager, didReceive data: Data, withID payloadID: PayloadID,
    from endpointID: EndpointID) {
    // We can ignore implementation, because we are the publisher.
  }

  func connectionManager(
    _ connectionManager: ConnectionManager, didReceive stream: InputStream,
    withID payloadID: PayloadID,
    from endpointID: EndpointID, cancellationToken token: CancellationToken) {
    // We can ignore implementation, because we are the publisher.
  }

  func connectionManager(
    _ connectionManager: ConnectionManager, didStartReceivingResourceWithID payloadID: PayloadID,
    from endpointID: EndpointID, at localURL: URL, withName name: String,
    cancellationToken token: CancellationToken) {
    // We can ignore implementation, because we are the publisher.
  }

  func connectionManager(
    _ connectionManager: ConnectionManager, didReceiveTransferUpdate update: TransferUpdate,
    from endpointID: EndpointID, forPayload payloadID: PayloadID) {
    // We can ignore implementation, because we are the publisher.
  }

  func connectionManager(
    _ connectionManager: ConnectionManager, didChangeTo state: ConnectionState,
    for endpointID: EndpointID) {
    switch state {
    case .connecting:
      // A connection to the remote endpoint is currently being established.
    case .connected:
      // We're connected! Can now start sending and receiving data.
      connectionManager.send(<MESSAGE_DATA>, to: [endpointID])
    case .disconnected:
      // We've been disconnected from this endpoint. No more data can be sent or received.
    case .rejected:
      // The connection was rejected by one or both sides.
    }
  }
}

extension Publisher: AdvertiserDelegate {
  func advertiser(
    _ advertiser: Advertiser, didReceiveConnectionRequestFrom endpointID: EndpointID,
    with context: Data, connectionRequestHandler: @escaping (Bool) -> Void) {
    // Automatically accept any incoming connection requests.
    connectionRequestHandler(true)
  }
}

Subscriber

class Subscriber {
  let connectionManager: ConnectionManager
  let discoverer: Discoverer

  init() {
    connectionManager = ConnectionManager(serviceID: <SERVICE_ID>, strategy: .pointToPoint)
    connectionManager.delegate = self

    discoverer = Discoverer(connectionManager: connectionManager)
    discoverer.delegate = self
  }

  func startSubscription() {
    discoverer.startDiscovery()
  }

  func stopSubscription() {
    discoverer.stopDiscovery()
  }
}

extension Subscriber: ConnectionManagerDelegate {
  func connectionManager(
    _ connectionManager: ConnectionManager, didReceive data: Data, withID payloadID: PayloadID,
    from endpointID: EndpointID) {
    // This always gets the full data of the payload.
    print(data)
  }

  func connectionManager(
    _ connectionManager: ConnectionManager, didReceive stream: InputStream,
    withID payloadID: PayloadID,
    from endpointID: EndpointID, cancellationToken token: CancellationToken) {
    // We can ignore implementation because we only care about bytes payloads.
  }

  func connectionManager(
    _ connectionManager: ConnectionManager, didStartReceivingResourceWithID payloadID: PayloadID,
    from endpointID: EndpointID, at localURL: URL, withName name: String,
    cancellationToken token: CancellationToken) {
    // We can ignore implementation because we only care about bytes payloads.
  }

  func connectionManager(
    _ connectionManager: ConnectionManager, didReceiveTransferUpdate update: TransferUpdate,
    from endpointID: EndpointID, forPayload payloadID: PayloadID) {
    // Bytes payloads are sent as a single chunk, so you'll receive TransferUpdate.success
    // immediately.
  }

  func connectionManager(
    _ connectionManager: ConnectionManager, didChangeTo state: ConnectionState,
    for endpointID: EndpointID) {
    switch state {
    case .connecting:
      // A connection to the remote endpoint is currently being established.
    case .connected:
      // We're connected! Can now start sending and receiving data.
    case .disconnected:
      // We've been disconnected from this endpoint. No more data can be sent or received.
    case .rejected:
      // The connection was rejected by one or both sides.
    }
  }
}

extension Subscriber: DiscovererDelegate {
  func discoverer(_ discoverer: Discoverer, didFind endpointID: EndpointID, with context: Data) {
    // An endpoint was found. We request a connection to it.
    discoverer.requestConnection(to: endpointID, using: <ENDPOINT_INFO>)
  }

  func discoverer(_ discoverer: Discoverer, didLose endpointID: EndpointID) {
    // A previously discovered endpoint has gone away.
  }
}