Interface INearbyConnectivity
- Namespace
- Plugin.Maui.NearbyConnectivity
- Assembly
- Plugin.Maui.NearbyConnectivity.dll
Provides the entry point to nearby connectivity: advertises this device, discovers others, establishes connections, and exposes every device's lifecycle through a single collection.
public interface INearbyConnectivity
Remarks
Registered as a singleton, because there is one radio and one native session per device. The dependency injection container owns the instance. Call StopAllAsync() to return the session to its initial state rather than disposing it.
Advertising and discovery are Advertiser and Discoverer. This type holds what belongs to neither: availability, presence, the devices, the connections, and the payloads they receive.
A thing you watch is a property plus an event: subscribe, then read the property. A thing you consume, which needs one owner and backpressure, is a stream. Only payloads are consumed, through PayloadsAsync(CancellationToken).
Every member is callable from any thread, and nothing here has UI thread affinity. Events are raised one at a time on a thread-pool thread, never the UI thread and never the platform SDK's own callback thread. A caller that binds to a user interface marshals for itself, or constructs a NearbyDeviceCollection or NearbySessionState. Unsubscribe when the subscriber goes away: the session lives as long as the application.
Android and iOS only. On every other target framework, CheckAvailabilityAsync(CancellationToken) reports Unsupported and StartAsync(CancellationToken) throws NearbyException.
Properties
Advertiser
Gets advertising: start and stop it, watch its state, and set how inbound requests are answered.
INearbyAdvertiser Advertiser { get; }
Property Value
- INearbyAdvertiser
The same instance for the life of the session.
Availability
Gets what currently prevents nearby connectivity, if anything.
NearbyAvailability Availability { get; }
Property Value
- NearbyAvailability
Available when nothing blocks it, or the worst blocking condition. Never throws.
Remarks
A cached snapshot, refreshed as the platform reports changes, which raises AvailabilityChanged. Call CheckAvailabilityAsync(CancellationToken) to force a fresh read.
Connections
Gets the connections currently open.
IReadOnlyList<NearbyConnection> Connections { get; }
Property Value
- IReadOnlyList<NearbyConnection>
The open connections. Enumerating yields an immutable snapshot, so it is safe to read from any thread.
Remarks
A snapshot for acting on every open connection at once, such as sending to everyone. To watch connections open and close, handle DeviceChanged and read Connection. Read this at the moment of use rather than caching it: a connection that drops simply stops appearing.
Devices
Gets the devices known to this session, from first discovery until they are no longer visible.
IReadOnlyList<NearbyDevice> Devices { get; }
Property Value
- IReadOnlyList<NearbyDevice>
The current set of devices. Enumerating it yields an immutable snapshot, so it is safe to read from any thread.
Remarks
A single collection spans the whole device lifecycle. Devices do not move between collections as they connect. Status reports where a device is in that lifecycle. Subscribe to DeviceChanged first, then read this property, so no change falls between the two.
Discoverer
Gets discovery: start and stop it, watch its state, and refresh it.
INearbyDiscoverer Discoverer { get; }
Property Value
- INearbyDiscoverer
The same instance for the life of the session.
Presence
What nearby devices see for this device before any connection exists.
NearbyPresence Presence { get; }
Property Value
- NearbyPresence
The effective presence — what peers are seeing now. After SetPresence(NearbyPresence) is called while advertising, discovering, connected or connecting, this keeps reporting the old value until that change takes effect.
Remarks
Starts as Presence, which defaults to this device's own name with empty info.
- See Also
Methods
CheckAvailabilityAsync(CancellationToken)
Reads current availability from the platform.
Task<NearbyAvailability> CheckAvailabilityAsync(CancellationToken cancellationToken = default)
Parameters
cancellationTokenCancellationTokenA token to observe while checking.
Returns
- Task<NearbyAvailability>
A task whose result is Available when advertising and discovery can be started, or what is blocking them.
Remarks
Reports state; it does not change it, never prompts for permissions, and never enables a radio. Request permissions with NearbyPermission, and direct the user to system settings to enable a radio.
The result is a snapshot. The user can disable a radio immediately afterwards, so an Available result is not a guarantee that starting will succeed. StartAsync(CancellationToken) checks again for itself.
PayloadsAsync(CancellationToken)
Reads the inbound payloads from every connection as one stream.
IAsyncEnumerable<NearbyInboundPayload> PayloadsAsync(CancellationToken cancellationToken = default)
Parameters
cancellationTokenCancellationTokenA token to end the enumeration.
Returns
- IAsyncEnumerable<NearbyInboundPayload>
An IAsyncEnumerable<T> yielding each inbound payload exactly once, from every connection this session holds, paired with the device that sent it.
Examples
await foreach (var (device, payload) in nearby.PayloadsAsync(cancellationToken))
{
if (payload is NearbyBytesPayload bytes)
{
Handle(device, Encoding.UTF8.GetString(bytes.Data.Span));
}
}
Remarks
The application-wide receive loop. Connections already open are read from the moment the enumeration starts, and each new connection is picked up as it opens, so a caller that starts late misses neither a connection nor a payload buffered before it arrived.
Single-consumer. A payload is handed to one enumeration and then gone, so two enumerations of this stream split the payloads between them. Read in one place and fan out an application-level message from inside the loop.
The loop body provides backpressure per connection: the next payload from the same connection is not dequeued until the body returns. Connections are read concurrently, so a slow body for one peer never stalls another.
Canceling ends the wait, not the buffer. When a connection closes, everything it had already buffered is yielded before it leaves the stream. Canceling the token stops the enumeration once what is buffered has been handed over, so no payload is discarded by stopping.
Payloads arrive on a thread-pool thread; see the thread-safety notes on INearbyConnectivity.
SetPresence(NearbyPresence)
Changes what nearby devices see for this device.
void SetPresence(NearbyPresence presence)
Parameters
presenceNearbyPresenceThe new presence. Build it with new or copy the current one with with; NearbyPresence validates both members on construction, so an invalid one cannot reach this call.
Remarks
Takes effect the next time this device is idle — nothing advertising, nothing discovering, no connection open and none being negotiated. Called while idle, it applies at once and the next start announces it. Called while anything is running, it is held: peers keep seeing the old presence, and the new one takes effect once everything has stopped. Presence reports the effective value throughout, so it is the one to bind to rather than the one you passed.
The identity a peer connects to cannot change underneath it. iOS builds a local
MCPeerID from the display name and cannot rename a live one at all; Android would
otherwise advertise under one presence and send a connect request under another within a
single run. The idle boundary is what makes both platforms behave the same way.
To change it immediately, call StopAllAsync() first, then this, then start again.
Exceptions
- ArgumentNullException
presenceis null.
- See Also
StopAllAsync()
Stops advertising and discovery and disconnects every established connection, returning the session to its initial state.
Task StopAllAsync()
Returns
- Task
A task that completes when the session is fully stopped.
Remarks
The session remains usable afterwards. Advertising or discovery can be started again at any time.
Every connection is closed with SessionStopped, an outbound handshake still in flight fails with Failed, and each close inherits its own bounded wait for in-flight inbound work. With several Android connections open, this can take a few seconds per connection. A failure tearing one connection down is logged and does not stop the rest.
Takes no CancellationToken: teardown is work the caller wants to finish, and the waits it contains are already bounded internally. Stop first, then cancel whatever tokens drive your own streams.
Events
AvailabilityChanged
Occurs after Availability changes.
event EventHandler? AvailabilityChanged
Event Type
Remarks
Availability already holds the new value when it is raised. Re-read it in the handler. Raised on the event loop described on DeviceChanged.
DeviceChanged
Occurs after a device joins the set, changes, or leaves it.
event EventHandler<NearbyDeviceChange>? DeviceChanged
Event Type
Examples
nearby.DeviceChanged += (_, change) =>
{
if (change.Device.Status is NearbyDeviceStatus.RequestReceived)
{
// show the accept/decline row
}
};
Remarks
Subscribe first, then read Devices. The event carries changes, not history. A change is keyed by Id, so one already reflected in what you read is harmless to apply again.
Devices already holds the change when the event is raised. Handlers run one at a time on a single thread-pool thread, never the UI thread, the platform SDK's callback thread, or under a session lock. Every event this session raises shares that order. A slow handler delays the ones after it, and a handler that throws is logged while the rest still run.
Most applications bind a NearbyDeviceCollection instead of handling this.