Scanning, Connecting & the Current Network
IWifiManager covers the station side of the radio: what is in range, what you are joined to, and
moving between the two.
Scanning
Section titled “Scanning”var access = await wifi.RequestAccess(ct);if (access != AccessState.Available) return;
var networks = await wifi.Scan(ct);Results come back strongest first, one entry per BSSID — not per SSID. A multi-band router or a mesh network answers on several radios, so the same name appears more than once. Group them if you are building a picker:
var forDisplay = networks .GroupBy(x => x.Ssid) .Select(g => g.MaxBy(x => x.SignalStrengthPercent)!) .Where(x => !x.IsHidden) .ToList();WifiNetwork
Section titled “WifiNetwork”| Member | Notes |
|---|---|
Ssid |
Empty for a hidden network that did not broadcast one |
Bssid |
The MAC of the radio that answered; null where the platform withholds it |
Security |
Open, Wep, WpaPsk, Wpa2Psk, Wpa3Psk, Enterprise, Owe, Unknown |
SignalStrengthDbm |
Typically -30 (excellent) to -90 (unusable). Null on Linux, which reports only a quality percentage |
SignalStrengthPercent |
0-100, populated on every platform — the safe one to display |
FrequencyMhz |
Centre frequency |
Band / Channel |
Derived from the frequency |
IsHidden |
The access point does not broadcast its SSID |
IsOpen |
No passphrase needed. WEP counts as secured here, but treat it as open in practice |
iOS has no scan
Section titled “iOS has no scan”There is no public API. The only thing that lists nearby networks is NEHotspotHelper, whose
entitlement Apple grants case by case to captive-network-assistant apps. Scan() throws
WifiNotSupportedException on iOS and Mac Catalyst — check
Capabilities.HasFlag(WifiCapabilities.Scan) and offer a “join by name” field instead.
Scan throttling
Section titled “Scan throttling”Android throttles startScan from API 29 (roughly four scans per two minutes in the foreground) and
NetworkManager refuses a scan requested within about ten seconds of the last one. Both serve cached
results instead of failing, so a rapid second scan returns the previous sweep rather than an error.
Do not poll.
Connecting
Section titled “Connecting”var request = new WifiConnectionRequest("Kitchen"){ Passphrase = "hunter2hunter2", Remember = true, Timeout = TimeSpan.FromSeconds(20)};
try{ var joined = await wifi.Connect(request, ct);}catch (WifiConnectionException ex){ // wrong passphrase, out of range, user declined the prompt, or DHCP never answered}Connect returns only once an address has been assigned. Association completes well before DHCP
does, and a WifiNetworkInfo with no IP on it is not much use to the caller.
What each platform does with the request
Section titled “What each platform does with the request”| Field | Android | iOS / Catalyst | macOS | Windows | Linux |
|---|---|---|---|---|---|
Ssid, Passphrase |
✅ | ✅ | ✅ | ✅ | ✅ |
Security |
✅ (picks WPA2 vs WPA3 key mgmt) | WEP flag only | read from beacon | read from beacon | ✅ |
Bssid |
✅ | ignored | ✅ | ✅ | ✅ |
IsHidden |
✅ | ✅ | ✅ | ✅ | ✅ |
Remember |
✅ API 30+ (adds a suggestion) | ✅ (JoinOnce inverted) |
✅ | ✅ | ✅ |
Leave Security as Unknown unless the network is hidden. The platform reads the scheme off the
beacon; a hidden network has no beacon to read, so it has to be told.
Remember saves the network so it can be rejoined later, and is what puts it in
GetKnownNetworks(). Windows, macOS and Linux write an ordinary profile;
iOS keeps the hotspot configuration; Android 11+ registers a WifiNetworkSuggestion next to the
join, because the specifier-based join itself is never persisted.
Disconnecting
Section titled “Disconnecting”await wifi.Disconnect(ct);What this means varies more than the name suggests:
- Android 10+ and iOS drop the network your app asked for. The OS is then free to rejoin one the user had already saved, so the device may not end up offline at all.
- Windows, macOS and Linux disassociate the adapter outright.
The current network
Section titled “The current network”var current = wifi.CurrentNetwork;if (current != null){ Console.WriteLine(current.Ssid); Console.WriteLine(current.IPv4Address); Console.WriteLine(current.Gateway); Console.WriteLine(String.Join(", ", current.DnsAddresses));}WifiNetworkInfo carries Ssid, Bssid, Security, SignalStrengthDbm,
SignalStrengthPercent, FrequencyMhz, Band, Channel, InterfaceName, IpAddresses,
DnsAddresses, Gateway, SubnetMask, and the IPv4Address / IPv6Address shortcuts.
CurrentNetwork is read live off the OS on every access, so it is always current but is not free —
hold the result rather than re-reading it in a loop. The one exception is Linux, where D-Bus is
asynchronous: the value is cached and refreshed from the NetworkManager watcher, and only the first
read pays for a blocking round trip.
Watching for changes
Section titled “Watching for changes”public sealed class NetworkWatcher(IWifiManager wifi) : IDisposable{ public void Start() => wifi.Changed += this.OnChanged; public void Dispose() => wifi.Changed -= this.OnChanged;
void OnChanged(object? sender, WifiNetworkInfo? network) => this.status = network == null ? "Offline" : $"{network.Ssid} ({network.SignalStrengthPercent}%)";}Changed fires with the new network, or null when the device drops off Wi-Fi entirely.
- It is de-duplicated. The native watchers behind it — Android’s
NetworkCallback, Apple’sNWPathMonitor, NetworkManager’sPropertiesChanged— all fire several times for one real change. Only genuine differences are raised. WifiNetworkInfocompares its address lists by value, not by reference, so diffing snapshots yourself works too. The record’s synthesized equality would have compared the arrays by reference and made every poll look like a change.- Unsubscribe. The native watcher is created on the first subscription and torn down on the last, so a leaked handler keeps a radio callback alive for the life of the process.
Powering the radio
Section titled “Powering the radio”if (wifi.Capabilities.HasFlag(WifiCapabilities.RadioToggle)) await wifi.SetRadioEnabled(true, ct);
var isOn = await wifi.GetRadioEnabled(ct);Android revoked setWifiEnabled for third-party apps in API 29 — the capability flag is only set
below that, and above it you should send the user to Settings.Panel.ACTION_WIFI. iOS never allowed
it and does not report the state either. Windows, macOS and Linux support both.
Known networks
Section titled “Known networks”Networks the device has already saved are covered on their own page — Known Networks — including the important caveat that iOS and Android only ever disclose the entries your own app created.
Errors
Section titled “Errors”| Exception | Means | Recoverable |
|---|---|---|
WifiNotSupportedException |
The OS has no API for this. The message names the limit. | No — branch on Capabilities |
WifiPermissionException |
A permission, entitlement or manifest entry is missing. The message names it. | Yes |
WifiConnectionException |
The join failed or timed out | Yes |
WifiException |
The base type for all of the above | Depends |


