Bluetooth Low Energy

How do two phones find each other over Bluetooth?

One phone advertises, repeatedly broadcasting short packets that carry an identifier, usually a service UUID the app chose. The other phone scans, filters what it hears for that UUID and connects to the match. For discovery to work in either direction both phones run both roles, and each platform's permission and background rules decide when the other phone can be seen.

Learning objectives

After reading this article you will be able to:

  • Explain how advertising and scanning let two phones discover each other
  • Describe how a shared service UUID filters the right phone from other devices
  • Identify the iOS and Android background rules that stop a phone being found

Two jobs: advertise and scan

Bluetooth LE discovery always has two sides. One device advertises: it repeatedly broadcasts small packets that say “I am here” and carry a little data. The other device scans: it listens for those packets and reports what it hears to the app.

These are the two BLE roles. The peripheral advertises and accepts connections; the central scans and starts them. Android’s BLE overview points out the consequence for phones: two devices that only support the central role cannot talk to each other, and neither can two that only support the peripheral role. A phone talking to a fitness tracker only needs to scan, because the tracker advertises. For two phones to meet, at least one of them has to advertise.

The identifier both apps agree on

A phone hears a lot of advertisements: headphones, watches, trackers and other phones. To pick out the right one, both copies of the app are built with the same service UUID, a 128-bit identifier that the peripheral side puts in its advertisement.

Advertisements are small, so the identifier has to fit. Android’s BluetoothLeAdvertiser reference says an advertiser can broadcast up to 31 bytes of advertisement data. On iOS, Apple’s startAdvertising(_:) reference allows only two keys, the local name and the service UUIDs, and gives a foreground app up to 28 bytes in the initial advertisement, plus 10 bytes in the scan response for the local name only, not counting 2 bytes of header per data type.

The scanning side then filters on that UUID:

  • iOS. scanForPeripherals(withServices:options:) takes a list of service UUIDs and reports only peripherals that advertise them. Apple calls this the recommended practice over passing nil, which reports every peripheral it finds.
  • Android. startScan() accepts a list of ScanFilter objects that restrict what the scan looks for, along with ScanSettings that set its parameters.

From discovery to connection

A matching advertisement is only the start. The scanning phone then:

  1. Connects to the advertising phone.
  2. Discovers its services and characteristics through GATT.
  3. Writes to a characteristic to send, and subscribes to notifications to receive.

Scanning costs power, and both platforms say so. Android’s guide says to stop scanning as soon as you find the device you want, never to scan on a loop, and always to set a time limit on a scan. Apple builds the same thinking into iOS: in the background, repeated advertisements from one peripheral are merged into a single discovery and scans run less often, changes Apple says help minimise radio use and improve battery life.

When one phone is in the background

Discovery is most reliable when both apps are open. Off screen, each platform changes the rules.

iOS. An app needs the bluetooth-central and bluetooth-peripheral background modes to scan and advertise in the background at all. Even then, a background scan must name the service UUIDs it wants. A backgrounded advertiser drops its local name and places all its service UUIDs in an “overflow” area that, in Apple’s words, can be discovered only by an iOS device explicitly scanning for them. Can a BLE app run in the background on iOS? goes through these limits.

Android. The BluetoothLeScanner reference says unfiltered scans stop when the screen turns off and resume when it turns on, and that filtered scans avoid this. Scanning and advertising also need the right runtime permissions, which Bluetooth permissions on Android, explained covers.

For a mixed pair, the iOS overflow area matters most: an Android phone scanning for your UUID may not see an iPhone whose app is in the background. How do iPhone and Android phones message each other over Bluetooth? looks at that case.

Both phones, both roles

If either person might open the app first, each phone has to advertise and scan at the same time. That brings one more design choice: when both phones discover each other, the app needs a rule for which side opens the connection, so the pair does not connect twice.

It also rules out libraries that implement only the central role; Can react-native-ble-plx connect two phones directly? explains the gap. Offline Protocol’s React Native mesh SDK, for example, is a native module that advertises and scans over BLE on each phone, which is also why it needs a native build and does not run in Expo Go.

Frequently asked questions

Do the phones have to be paired in the Bluetooth settings?

No. Discovery through advertising and scanning happens inside the apps; Android's own scanning example finds and lists devices with no pairing step. Android's BLE overview also cautions that data exchanged with a paired device is accessible to all apps on the phone, so apps that carry sensitive data add their own app-layer security.

Why does my phone find the other one only when the app is open?

Usually because of background rules. A backgrounded iOS app advertises its service UUIDs only in an overflow area that other iPhones explicitly scanning for them can see, and scans less often. On Android, unfiltered scans stop when the screen is off. Filtered scans and the right background modes or services help, but test each case on real phones.

Sources

Build it with Offline Protocol

The two-phone example builds a React Native app on two physical phones, has each discover the other over Bluetooth LE in the foreground, and checks the discovered address and transport before sending a message.

Read the two-phone example