Why BLE Works on Android But Not iOS and How to Fix It

How BLE differences in Android and iOS create problem?

Your BLE device passed every test. Android connects cleanly, data comes through, and the demo looks great. Then someone picks up an iPhone and nothing works.

The connection drops. The scan finds nothing. The pairing screen never shows up. Everything you tested is broken, and your iOS deadline is two weeks away.

By the end of this post, you will know exactly why this happens and what to change in your firmware and app to stop it from happening again.

Why the Same BLE Device Acts Differently on iOS and Android

BLE (Bluetooth Low Energy) is a standard. The rules for how devices find each other, connect, send data, and disconnect are the same everywhere. Every chip — nRF52, ESP32, STM32WB - follows the same rules. So the chip is not the problem.

The problem is what iOS and Android do on top of those rules.

Android lets developers work close to the Bluetooth layer. You can scan whenever you want, set connection speed, and talk to devices with very few restrictions. It is flexible, sometimes to a fault.

iOS puts a layer called CoreBluetooth between your app and the Bluetooth stack. CoreBluetooth is stable and predictable. But Apple decided what apps can and cannot do - when they can scan, how fast they can connect, what happens when the app is not on screen. Those decisions are exactly where your product breaks.

The two platforms are not doing the same thing under the hood. Expecting them to act the same is where most BLE projects go wrong.

The Six Things That Break BLE Products on iOS

1. iOS Hides the Device Address - Android Shows It

Every BLE device has a hardware address - think of it like a MAC address on a Wi-Fi router. It is a unique ID you can use to find and reconnect to a specific device.

Android shows you that address. Your app can save it, use it to search for the device, and reconnect to it later.

iOS does not show it. Instead, CoreBluetooth gives every device a random ID that only works on that specific iPhone. The same device gets a different ID on a different iPhone. That ID also changes over time.

So if your app or firmware uses the hardware address to track or reconnect to a device - it will work on Android and fail on iOS every time.

Fix: Stop using the hardware address to identify your device. Instead, give your device a unique service ID (called a service UUID) that it always advertises. Build your reconnection logic around that. iOS can find your device by service ID even when it cannot see the hardware address.

2. iOS Will Not Scan in the Background Unless You Tell It What to Look For

On Android, your app can search for nearby BLE devices in the background without any setup. It just works.

On iOS, background scanning has a rule: your app must tell iOS in advance exactly which service IDs it is looking for. This is done by adding those IDs to a settings file in your app called Info.plist. If that setup is missing, iOS will not wake your app when the device is nearby - even if the device is advertising and the phone is in range. The scan just returns nothing.

This is not a bug. Apple does this to save battery. iOS does not let apps scan for everything all the time in the background. It only wakes your app for Bluetooth activity if your app pre-registered which devices it cares about.

Fix: Add every service ID your device advertises to the iOS app's background settings before you test background scanning. If you change those IDs during development, update that settings file to match.

3. iOS Ignores Your Firmware's Speed Request - Android Listens

When a BLE device connects, it can ask the phone to communicate at a certain speed. This is called the connection interval - basically how often the device and phone check in with each other. A shorter interval means faster data. A longer interval saves battery.

On Android, your app can request a specific connection speed and the phone generally follows it.

On iOS, your device can make the same request, but iOS decides what to do with it. Apple applies its own limits. iOS will not go below roughly 15ms for the check-in interval. Android can go as low as 7.5ms.

If your firmware asks for a very fast interval because it needs to move data quickly - iOS will either ignore the request or set its own slower value. Your data slows down. If your app was designed around fast data, it may start timing out.

Fix: Keep your connection speed request in a range that iOS accepts. For most products, a check-in interval between 20ms and 75ms works on both platforms. Test this on a real iPhone — the iPhone simulator does not behave like the real thing for Bluetooth.

4. Android Quietly Stops Scanning After 30 Seconds With No Filter

This one does not affect iOS - but it breaks Android in a way that is easy to mistake for a firmware problem.

From Android 7 onward, if your app scans for BLE devices without any filter, Android automatically stops the scan after 30 seconds. No warning. No error. The scan just stops finding devices. Your app looks like it is still scanning. Your device is still advertising. Nothing connects.

The fix is to always add a scan filter - tell Android to scan for a specific service ID, device name, or maker ID. If you cannot use a filter for some reason, stop and restart the scan every 25 seconds.

Fix on the firmware side: Make sure your device always advertises the same service ID so the Android app has something to filter on. If your device keeps changing what it advertises, the app cannot apply a stable filter and will hit the 30-second wall.

5. iOS Cuts Your App Off When It Goes to the Background

When a user switches to another app or locks their phone, iOS puts your app in the background. At that point, most Bluetooth activity stops - unless your app has declared that it needs to keep working with Bluetooth in the background.

Even if you do declare background Bluetooth, iOS can still shut your app down if the phone runs low on memory. When your app starts up again, it does not automatically reconnect. Your app has to do that manually - using a saved device ID from the last session.

If your app does not handle this restart properly, every time the user backgrounds the app, the connection is gone and does not come back.

Android is much more relaxed about this. Your app can keep a Bluetooth connection running in the background without much setup. The main thing to watch for on Android is that some phone brands apply aggressive battery saving that can interfere with background apps.

Fix: Build your iOS app to save the device ID before the connection is lost. When the app starts back up, use that ID to reconnect. On the firmware side, after a connection is lost, have the device start advertising again for a set period so the app has a window to reconnect when it comes back.

6. iOS Is Strict About Sending Commands One at a Time

When your app talks to a BLE device, it sends commands - read this value, write that value, subscribe to this notification. These commands are called GATT operations.

On Android, developers sometimes send several commands in a row quickly. The Android Bluetooth stack handles this without complaining, though the results can be unpredictable.

On iOS, you must send one command and wait for the response before sending the next one. If you fire two commands at once, iOS drops the second one. No error. No warning. The data just never arrives.

This is the exact reason why code that works on Android breaks on iOS. Android was forgiving. iOS is strict. Both are correct but your app needs to be written for the stricter one.

Fix: Build a command queue in your app. Before sending the next command, wait for the response from the previous one. Do this on both iOS and Android. It is the correct way to use BLE and it removes a whole class of bugs.

What the Firmware and App Need to Agree On - Before You Write a Single Line of Code

Most BLE bugs are not Bluetooth bugs. They are agreement bugs. The firmware does one thing. The app expects something different. On Android, the gap often gets covered up - the stack is forgiving, data arrives anyway, and nobody notices. On iOS, the same gap causes a hard failure with no error message to explain why.

Here are the firmware-to-app integration decisions that catch teams off guard on both platforms.

Your Data Format Needs a Version Number From Day One

When your firmware sends data to the app, that data has a structure - a certain number of bytes, in a certain order, meaning certain things. The app reads that structure and makes sense of it.

This works fine until the firmware changes. Maybe you add a new sensor. Maybe you reorganise the bytes to make room for more data. Now the app is reading the old structure but the firmware is sending the new one. The numbers come out wrong. The app shows bad data or crashes.

On Android, this kind of mismatch sometimes still "works" because developers notice early and patch quickly. On iOS, if the app is live on the App Store, a firmware update can break every existing user until you ship a new app version - which takes Apple review time on top of your fix time.

What to do: Add a version byte to every data packet your firmware sends. It can be a single byte at the start — just a number like 01 or 02. The app reads that byte first, then decides how to read the rest of the packet based on which version it sees. When the firmware changes, bump the version number. The app can support both old and new formats at the same time during the transition.

Notifications vs Polling - Pick One and Stick To It

There are two ways for a BLE app to get data from a device. The first is polling - the app asks the device for its latest reading every few seconds. The second is notifications - the device pushes data to the app the moment something changes.

Both work. But teams sometimes mix them - the firmware is set up for notifications, the app is polling. Or vice versa. On Android, polling a device that is set up for notifications often still returns data, so the bug hides. On iOS, CoreBluetooth is strict, if the firmware expects the app to subscribe to a notification and the app never does, the data never moves.

What to do: Decide up front whether each piece of data uses notifications or polling. Write that down as part of your GATT design - the plan for what data lives where on the device. Make sure the firmware and app teams are looking at the same document. For real-time data like sensor readings, use notifications. For data that only changes on request like settings or configuration, use read/write. Do not mix them without a clear reason.

Packet Size Matters More Than Most Teams Expect

BLE does not send data the way Wi-Fi does. It breaks data into small chunks called packets. By default, each packet is 20 bytes. That is not a lot. If you need to send more data - a firmware update file, a long log, a large config - the firmware and app need to agree on how to break that data into 20-byte pieces and put it back together on the other side.

You can increase the packet size by negotiating a larger MTU (Maximum Transmission Unit - basically, the largest chunk size both sides agree to support). Android negotiates this fairly reliably. iOS negotiates it too, but the exact size you end up with depends on the iPhone model and iOS version.

The problem happens when the firmware assumes a larger packet size that was never actually negotiated. It sends 100-byte chunks. The app only agreed to 20. On Android, this often still works because the Android stack quietly handles the mismatch. On iOS, the data arrives broken or not at all.

What to do: Always negotiate the packet size explicitly at connection time - do not assume it. After connecting, both the firmware and the app should go through a quick handshake to agree on the chunk size they will use. Then both sides use that agreed size and nothing else. If the negotiation fails, fall back to 20 bytes rather than guessing.

Error Handling Has to Work Both Ways

When something goes wrong on the device - a sensor fails, a buffer overflows, the battery drops too low - the firmware needs a way to tell the app. And when something goes wrong on the app side - a write fails, a read times out - the app needs to tell the user clearly what happened.

Most teams handle errors on the firmware side. Fewer teams handle errors on the app side. On Android, write failures sometimes get quietly retried by the stack, so the app never even sees them. On iOS, a failed write returns an error immediately and the app has to do something with it. If the app has no error handling, it freezes or crashes.

What to do: Give your firmware a dedicated status characteristic - a small data field the device updates whenever something important changes, like battery level, error state, or connection health. The app subscribes to this at connection time and reacts when it changes. On the app side, every write, read, and subscribe call needs a failure path - not just a success path. If a command fails, retry once. If it fails again, show the user something useful rather than going silent.

OTA Firmware Updates Need a Protocol - Not Just a File Transfer

At some point, you will need to update the firmware on devices that are already in the field. This is called OTA - Over The Air update. It means sending a new firmware file from the app to the device over BLE, while the device is running.

OTA is one of the hardest things to get right across both platforms. The update file is large - often 100KB to 500KB or more. BLE is slow. The transfer takes time. During that time, if the phone locks, the app goes to background, or the connection drops, the update can fail halfway through. A device with a half-written firmware is usually a bricked device.

On Android, your app can keep running in the background during the transfer with a background service. On iOS, the app can be suspended by the system at any point. If iOS suspends the app mid-transfer and your firmware has no way to resume from where it left off, you have a corrupted device in a customer's hands.

What to do: Use a proven OTA library rather than writing your own transfer protocol. Nordic's DFU (Device Firmware Update) library and the Nordic Semiconductor SDK both have iOS and Android implementations that handle the hard parts - chunking the file, resuming after interruption, verifying the image before applying it. If your chip supports it, this is a solved problem. Do not solve it again from scratch.

The Connection State the App Sees Is Not Always the Real State

This is one of the most confusing bugs in BLE development, and it affects both platforms.

When a BLE connection drops - because the device went out of range, the battery died, or the user walked away - the app does not always find out immediately. The app may still show "connected" for several seconds while the underlying connection is already gone. On Android, some phone models take up to 10–30 seconds to report a disconnection. On iOS, CoreBluetooth usually reports it faster, but the timing still varies.

The real problem comes when the app tries to send data during that window. The write appears to succeed. The app moves on. But the data never reached the device because the connection was already dead.

What to do: Do not trust the connection state alone. Build a heartbeat into your protocol - a small message the firmware sends to the app every few seconds just to confirm it is still there. If the app does not receive a heartbeat for a set time (say, 5–10 seconds), it treats the connection as lost and starts reconnecting - regardless of what the OS connection state says. This works reliably on both platforms and catches the gaps that the OS state reporting misses.

Integration decision

What to agree on before building

What breaks if you skip it

Data format versioning

Add a version byte to every packet

Firmware update breaks all existing app users

Notifications vs polling

Decide per characteristic before writing any code

Data never moves on iOS if the app never subscribes

Packet size

Negotiate at connection time, fall back to 20 bytes

Data arrives broken or not at all on iOS

Error handling

Firmware reports errors via status characteristic; app handles write/read failures

App freezes or crashes silently on iOS

OTA updates

Use a proven library with resume support

Half-written firmware bricks the device on iOS

Connection state

Add a heartbeat - do not rely on OS state alone

App sends data to a dead connection and silently loses it

Before You Build: Check Points for BLE Products That Work on Both Platforms

Use this before you write the first line of firmware or app code.

On the firmware side:

  • Always advertise the same service ID - do not rotate it

  • Keep your connection speed request in the iOS-safe range (20–75ms interval, skip count 0–4, timeout 2–6 seconds)

  • Identify your device by service ID, not hardware address

  • After a connection drops, start advertising again for at least 30–60 seconds so the app can reconnect

  • Add a version byte to every data packet so the app knows how to read it

  • Set up a status characteristic the device updates when something important changes — battery low, sensor error, ready state

  • Send a heartbeat message every few seconds so the app knows the connection is truly alive

  • Use notifications for real-time data, read/write for settings - do not mix them without a clear reason

  • Add a test mode or debug characteristic so you can check behaviour on both platforms without guessing

On the iOS app side:

  • Add all service IDs to the background settings file before testing background scan

  • Build connection save and restore from day one - do not add it after launch

  • Build a command queue - send one command, wait for the response, then send the next

  • Never track devices by hardware address - track by service ID or a custom identifier

  • Negotiate packet size at connection time - do not assume 20 bytes or a larger size

  • Subscribe to the device's status characteristic right after connecting

  • Handle write and read failures explicitly - retry once, then show the user something useful

  • If your product does OTA updates, use a proven library with resume support

  • Test on a real iPhone - the simulator does not behave like real CoreBluetooth

On the Android app side:

  • Always use a scan filter - filter by your firmware's service ID

  • If you need to scan without a filter, restart the scan every 25 seconds

  • Test on at least 3–4 different phone brands - Samsung, Pixel, and Xiaomi can behave differently

  • Handle background scanning restrictions for Android 8 and above

  • Do not rely on the OS connection state alone - use a heartbeat to confirm the device is still reachable

The Right Time to Catch These Problems Is Before You Build

Most BLE products that break on iOS were never designed with Apple's rules in mind. The firmware was built the way Android works — fast, flexible, forgiving. The iOS problems showed up in testing, late in the project, when changing the firmware is painful and expensive.

Apple has documented its rules. The gap is not a mystery — it is just that most teams do not read it until something breaks. If you design for iOS limits from the start, Android takes care of itself.

If you are planning a BLE product — a wearable, a medical device, an industrial sensor, or any connected device — and you want firmware and mobile app development that handles both platforms from day one without surprises, CoreFragment's team has built BLE products across healthcare, wearables, and IoT. We can look at your GATT profile and firmware design early and catch the iOS compatibility gaps before they cost you a respin.

Author

Parthraj Gohil

Parthraj Gohil is the Founder and CEO of CoreFragment Technologies. He run the team of IoT developers, embedded engineers, app developers and AI engineers. With more than 10 years of industry experience, he has delivered projects across Healthcare IoT, Industrial IoT, Consumer IoT and AIoT.

Have Something on Your Mind? Contact Us : info@corefragment.com or +91 79 4007 1108

Share this blog

Share this on social channels to benefit others.