Table of Contents

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

cancellationToken CancellationToken

A 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

cancellationToken CancellationToken

A 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

presence NearbyPresence

The 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

presence is 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

EventHandler

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

EventHandler<NearbyDeviceChange>

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.

See Also