ble_peer_session 0.4.3 copy "ble_peer_session: ^0.4.3" to clipboard
ble_peer_session: ^0.4.3 copied to clipboard

PlatformAndroid

Offline 1:1 peer sessions over BLE for Flutter — host/client model, invitation handshake, and bidirectional messaging.

ble_peer_session #

Offline 1:1 peer sessions over BLE (host / client model)

Scope: 1:1 only (one host + one client). No Wi‑Fi or internet required.

Host–client connection flow: discover → invite → accept → connected (sendText / sendJson)

Use cases #

  • Local multiplayer games
  • Offline chat
  • Device-to-device pairing
  • Nearby collaboration tools
  • Prototyping peer-to-peer experiences

TL;DR #

  1. Host waits
  2. Client scans
  3. Client sends invite
  4. Host accept() → connected
  5. sendText / sendJson
Step Host Client
Start peer.host(localUser: …) peer.client(localUser: …)
Find peer waits nearbyHostsStream
Connect accept() on invite invite(host)
Chat sendText / sendJson same

Host                    Client

peer.host()             peer.client()
     │                       │
     ▼                       ▼
 Waiting               Discovering
     │                       │
     │<------ invite --------│
     │                       │
 accept()                    │
     │                       │
     └──── connected ────────┘
                │
        sendText / sendJson

Quick start #

Pick your role — host or client.

Host — wait for a friend #

import 'package:ble_peer_session/ble_peer_session.dart';

final peer = Peer.create(appName: 'MyGame');

final host = await peer.host(
  localUser: PeerUser(id: 'me', displayName: 'Alice'),
);

host.messagesStream.listen((message) {
  if (message.type == PeerMessageTypes.sessionInvite) host.accept();
});

host.textMessages.listen(print);
await host.sendText('Room is ready');

Client — find host and say hello #

import 'package:ble_peer_session/ble_peer_session.dart';

final peer = Peer.create(appName: 'MyGame');

final client = await peer.client(
  localUser: PeerUser(id: 'me', displayName: 'Bob'),
);

client.nearbyHostsStream.listen((hosts) {
  if (hosts.isEmpty) return;
  client.invite(hosts.first);
});

client.textMessages.listen(print);
await client.sendText('Hello!');

Setup #

final peer = Peer.create(appName: 'MyGame');

// Optional: check Bluetooth before starting
if (peer.adapterStatus == PeerAdapterStatus.disabled) {
  // show "Enable Bluetooth" UI
}

// Android 12+
await peer.permissions.checkPermissions();

Core model #

Think in people and invitations, not BLE.

  1. Host waits.
  2. Client discovers hosts.
  3. Client sends invite.
  4. Host accepts.
  5. Both sides exchange messages.

Advertising, GATT and framing are handled internally.

Common misconception #

This is not a socket connection. It is an invitation-based session over BLE advertising + GATT.

Phases (connectionStreamPeerConnectionPhase):

  • waitingForPeer — host advertising / client browsing
  • awaitingUserDecision — host sees invite, call accept() or reject()
  • awaitingRemoteDecision — client sent invite, waiting
  • connected — send messages

Design principles #

  • People-first (host / client)
  • No BLE exposure in the basic API
  • Opinionated defaults

API (Level 1) #

Recommended for most apps:

  • Peer.create(appName: 'MyGame')
  • peer.host(localUser: …) / peer.client(localUser: …)
  • sendText() / textMessages
  • sendJson(type, map) / jsonMessages

Identity: PeerUser(id: '…', displayName: '…'). Nearby host: PeerNearby in nearbyHostsStream.


Advanced API (Level 2) #

Only if you need control over UUIDs or raw BLE transport.

Advanced features

  • Custom UUIDs
  • Raw endpoints
  • Direct device connections
  • Custom PeerMessage payloads
final peer = Peer.create(
  config: BlePeerConfig(
    appName: 'MyGame',
    serviceUuid: '0000180d-0000-1000-8000-00805f9b34fb',
    characteristicUuid: '00002a37-0000-1000-8000-00805f9b34fb',
  ),
  logger: myLogger,
);
  • Wire endpoint — startWithEndpoint / startDiscoveryWithEndpoint
  • Raw device — connect(device)
  • Any payload — PeerMessage.app(type: '…', payload: …)
  • Session types — PeerMessageTypes.sessionInvite, etc.

Custom app messages:

await host.sendJson('game.move', {'row': 1, 'column': 2});

// Or full control
await host.send(
  PeerMessage.app(
    sender: host.localEndpoint!,
    type: 'game.move',
    payload: {'row': 1, 'column': 2},
  ),
);

Reserved session types (PeerMessageTypes.*) are handled automatically during handshake.


Platform setup #

Android #

Required permissions — add to AndroidManifest.xml:

<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />

Common issue: if discovery doesn't work, check location and Bluetooth permissions on Android 12+ (await peer.permissions.checkPermissions()).

Bluetooth adapter #

The package does not turn Bluetooth on. Observe status and guide the user:

peer.adapterStatusStream.listen((status) {
  switch (status) {
    case PeerAdapterStatus.disabled:
      // prompt user
    case PeerAdapterStatus.enabled:
      // ready
    default:
      break;
  }
});

Example app #

example/minimal_chat demonstrates host/client roles and text chat with zero custom UUID setup.

cd example/minimal_chat
flutter pub get
flutter run

On two physical devices:

  1. Install the app on both phones and grant Bluetooth permissions.
  2. Device A: Host — wait for friend.
  3. Device B: Client — find host, tap the discovered host.
  4. Host auto-accepts the invite; send messages both ways.

Same appName (MinimalChat in the example) is required so both sides share service UUIDs.


Errors #

All failures throw PeerException with PeerErrorCode. See doc/ERROR_CODES.md.


Internals (for contributors) #

You don't need the sections below unless you implement a custom client or debug BLE issues.

Advertising
      │
      ▼
Discovery
      │
      ▼
GATT Connection
      │
      ▼
Framed Messages
      │
      ▼
JSON Payloads

Message framing #

One GATT write/notify carries one frame. Logical JSON messages larger than the effective MTU are split automatically on the link layer — you do not need to chunk in app code.

Limit Default
Chunk payload 480 bytes
Max logical message 256 KiB

Oversized sends throw PeerException with PeerErrorCode.payloadTooLarge. Corrupt or incomplete frames emit PeerErrorCode.messageDecodeFailed.

Wire layout (big-endian):

[version:1][flags:1][messageId:2][chunkIndex:2][totalChunks:2][payload…]

Legacy peers that send raw JSON without the framing header (version != 0x01) are still accepted on receive.


Migration guides #


License #

MIT

1
likes
160
points
625
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Offline 1:1 peer sessions over BLE for Flutter — host/client model, invitation handshake, and bidirectional messaging.

Repository (GitHub)
View/report issues

Topics

#bluetooth #ble #flutter #p2p #peer-to-peer

License

MIT (license)

Dependencies

bluetooth_low_energy, bluetooth_low_energy_android, device_info_plus, flutter, flutter_blue_plus, meta, permission_handler, url_launcher

More

Packages that depend on ble_peer_session