Google announced last week that the Nearby Connections API will stop automatically enabling Wi-Fi and Bluetooth radios on behalf of your app. This sounds like a footnote until you realize what the API is actually doing today — and what your connection flow silently depends on. If your app uses Nearby Connections for offline device-to-device communication, this is a behavioral breaking change that requires active code changes before late 2026.
Let me walk through exactly what's changing, why it matters for each connection strategy, and what the new pre-flight check pattern looks like in Kotlin.
What the Nearby Connections API Actually Does Under the Hood
The Nearby Connections API is a peer-to-peer networking library that handles offline, high-bandwidth device communication without a router or internet connection. Under the hood it multiplexes across Bluetooth Low Energy for discovery, Bluetooth Classic for lower-bandwidth payloads, and Wi-Fi Direct for high-bandwidth transfers — switching between them based on what the chosen strategy supports and what the hardware can sustain.
The key insight is that the API doesn't just use these radios — today it also turns them on if they're off. You call startAdvertising() or startDiscovery(), and if Bluetooth or Wi-Fi is disabled, the API quietly enables it in the background before proceeding. From a developer perspective this has always felt slightly magical: the connection works even when the user's radios are off. From a user perspective, their radio state is being changed without their knowledge.
That's what's going away.
The Specific Change: Radios Must Be On Before You Call
After the enforcement date in late 2026, startAdvertising(), startDiscovery(), and related Nearby Connections calls will not activate disabled radios. If your strategy requires Wi-Fi and Wi-Fi is off, the API will fail (or simply not connect) rather than turning Wi-Fi on for you. Same for Bluetooth.
Importantly, Google has confirmed this affects both first-party apps (Google's own internal uses of the API) and third-party developers equally. There's no private workaround for first parties.
The core principle: the Nearby Connections API will remain fully capable of using whatever radios the user has enabled. It's losing only the ability to change that state on the user's behalf. User privacy and transparency over hidden behavior.
Which Strategy Uses Which Radio
This is the part you need to understand before writing any pre-flight code. The three strategies have different radio profiles:
P2P_STAR — one central hub device that N spoke devices connect to. The hub role requires Wi-Fi Direct to support multiple concurrent connections at higher throughput. Spoke devices use Bluetooth for initial discovery and then negotiate up to Wi-Fi for data transfer if available. If you're building a local multiplayer game host, a classroom app, or a file-sharing hub, you're almost certainly on P2P_STAR.
P2P_CLUSTER — every device can be both hub and spoke simultaneously, forming a mesh-like topology. Lower maximum throughput than P2P_STAR but better for decentralized coordination. Bluetooth is sufficient for cluster operation, though Wi-Fi enables higher bandwidth paths when available.
P2P_POINT_TO_POINT — exactly two devices, highest possible throughput. Uses Wi-Fi Direct for the data channel. If you're doing file transfer or live media sync between two specific devices, this is the strategy — and it has the strictest radio requirements.
The practical consequence: if your strategy is P2P_POINT_TO_POINT and the user has Wi-Fi off, your connection simply cannot work after the change. Your app needs to detect this before attempting the connection, not after it fails.
The New Pre-Flight Pattern
The required pattern is a radio state check before every call into startAdvertising() or startDiscovery(). If the required radio is off, redirect the user to enable it — you can't do it for them.
import android.bluetooth.BluetoothManager
import android.content.Context
import android.net.wifi.WifiManager
import android.provider.Settings
fun checkRadiosBeforeConnect(context: Context, strategy: Strategy): RadioCheckResult {
val bluetoothManager = context.getSystemService(BluetoothManager::class.java)
val bluetoothAdapter = bluetoothManager?.adapter
val wifiManager = context.getSystemService(WifiManager::class.java)
val bluetoothOn = bluetoothAdapter?.isEnabled == true
val wifiOn = wifiManager?.isWifiEnabled == true
return when (strategy) {
Strategy.P2P_POINT_TO_POINT -> {
if (!bluetoothOn) RadioCheckResult.NeedsBluetooth
else if (!wifiOn) RadioCheckResult.NeedsWifi
else RadioCheckResult.Ready
}
Strategy.P2P_STAR -> {
if (!bluetoothOn) RadioCheckResult.NeedsBluetooth
else if (!wifiOn) RadioCheckResult.NeedsWifi
else RadioCheckResult.Ready
}
Strategy.P2P_CLUSTER -> {
if (!bluetoothOn) RadioCheckResult.NeedsBluetooth
else RadioCheckResult.Ready
}
else -> RadioCheckResult.Ready
}
}
sealed class RadioCheckResult {
object Ready : RadioCheckResult()
object NeedsBluetooth : RadioCheckResult()
object NeedsWifi : RadioCheckResult()
}
The check uses BluetoothAdapter.isEnabled() for Bluetooth state and WifiManager.isWifiEnabled() for Wi-Fi. Both are synchronous and don't require permissions beyond what Nearby Connections already demands. Note that WifiManager is deprecated for enabling Wi-Fi programmatically (that's the whole point — you shouldn't be doing it), but reading the state is fine.
Directing users to enable the radio
When a required radio is off, the recommended approach is a clear in-app message followed by a deep link into the system settings panel. Don't just crash or show a generic error — explain which radio is needed and why your feature depends on it:
fun promptUserToEnableRadio(context: Context, result: RadioCheckResult) {
when (result) {
is RadioCheckResult.NeedsBluetooth -> {
AlertDialog.Builder(context)
.setTitle("Bluetooth required")
.setMessage(
"Nearby sharing uses Bluetooth to find and connect " +
"to other devices. Please enable Bluetooth and try again."
)
.setPositiveButton("Open Settings") { _, _ ->
context.startActivity(
Intent(Settings.ACTION_BLUETOOTH_SETTINGS)
)
}
.setNegativeButton("Cancel", null)
.show()
}
is RadioCheckResult.NeedsWifi -> {
AlertDialog.Builder(context)
.setTitle("Wi-Fi required")
.setMessage(
"High-speed nearby transfer requires Wi-Fi. " +
"Please enable Wi-Fi and try again."
)
.setPositiveButton("Open Settings") { _, _ ->
context.startActivity(
Intent(Settings.ACTION_WIFI_SETTINGS)
)
}
.setNegativeButton("Cancel", null)
.show()
}
else -> {}
}
}
On Android 10 and above, Settings.Panel.ACTION_WIFI gives you a focused in-app panel for enabling Wi-Fi without leaving your app entirely, which is a better UX than full Settings. Worth using if your minSdk is high enough:
// Android 10+ (API 29+): focused Wi-Fi panel instead of full Settings
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
context.startActivity(Intent(Settings.Panel.ACTION_WIFI))
} else {
context.startActivity(Intent(Settings.ACTION_WIFI_SETTINGS))
}
Where This Shows Up in a Real Connection Flow
In a typical Nearby Connections flow, you call startAdvertising() on the host and startDiscovery() on the client, then handle connection lifecycle callbacks. The pre-flight check fits cleanly before either call:
fun startNearbySession(context: Context) {
val strategy = Strategy.P2P_STAR
val radioCheck = checkRadiosBeforeConnect(context, strategy)
if (radioCheck != RadioCheckResult.Ready) {
promptUserToEnableRadio(context, radioCheck)
return
}
val advertisingOptions = AdvertisingOptions.Builder()
.setStrategy(strategy)
.build()
Nearby.getConnectionsClient(context)
.startAdvertising(
localEndpointName,
serviceId,
connectionLifecycleCallback,
advertisingOptions
)
.addOnSuccessListener { /* advertising started */ }
.addOnFailureListener { e -> /* handle failure */ }
}
The guard is three lines. The dialog is boilerplate. The actual connection code is unchanged — you're just no longer relying on the API to silently handle state your app should be aware of anyway.
Don't gate the entire feature: if your app uses P2P_CLUSTER and Bluetooth is the only hard requirement, don't also block users when Wi-Fi is off. Check precisely what your chosen strategy needs and ask for only that. Over-asking trains users to dismiss your prompts.
What This Means If You Use HailUp or Similar Social/Event Apps
HailUp, which I built, uses peer-to-peer connections for event-local features — finding nearby users, proximity check-ins, local group coordination. That kind of social + location use case is a common Nearby Connections pattern: offline-first, user-proximate, works without a server round-trip.
The audit I'd do for any app in that space: find every call site for startAdvertising and startDiscovery in the codebase. Grep is enough. For each, trace back to what triggers it — a button tap, a lifecycle callback, an automatic background start. The button-tap case is simple: add the pre-flight check in the click handler. The automatic-start case is where things get interesting. If your app auto-initiates discovery when a certain screen opens, you can no longer assume the radios are on — you need to degrade gracefully or surface a prompt before the screen fully loads.
The harder pattern to catch is where you start discovery in onResume() without explicit user intent. That's a flow where the radio state was previously invisible (API handled it) and will now need to become visible (your UI surfaces it). A status indicator — "Bluetooth required for nearby features" — is often the right UX solution rather than an intrusive dialog on every resume.
Timeline and What to Do This Week
Google says enforcement takes effect in late 2026. That sounds distant until you remember that most teams ship monthly at best, and any non-trivial UX change (pre-flight dialogs, degraded states, onboarding prompts) needs design review and QA. "Late 2026" is roughly three to four release cycles for a typical Android team.
What I'd do in the next sprint:
- Audit call sites. Search for
startAdvertisingandstartDiscoveryacross your codebase. You may have more than you think — especially if a Nearby integration was added a few years ago and touched by multiple developers since. - Classify trigger type. Is each call site user-initiated (button, deep link) or automatic (lifecycle, background)? Automatic starts need UI treatment; user-initiated starts need a pre-flight guard.
- Map strategy to radio requirements. P2P_POINT_TO_POINT and P2P_STAR need both Bluetooth and Wi-Fi for full capability. P2P_CLUSTER can operate on Bluetooth alone. Don't over-ask.
- Write the check and the prompt. The code above is a complete starting point. Adapt the messaging to your app's voice.
- Test with radios off. This is the easy step teams skip. Disable Bluetooth and Wi-Fi in your emulator or test device before running the relevant flow. See what actually happens today. Then build and verify your fix handles it correctly.
The change itself is well-reasoned — users should know when an app is touching their radio state. What's frustrating is that the API gave developers no indication this was happening, so the dependency is invisible until enforcement. The good news is the fix is genuinely straightforward. It's not a migration or an API replacement — it's a pre-flight check and a user prompt. The hard part is finding all the places you need to add it.
No comments yet. Be the first to leave one!