Bind to a MAUI page
Two types replace the event handlers and the dispatch code that you would write by hand. Both raise
PropertyChanged on the UI thread, and the owner of each one disposes it. Each constructor takes
the IDispatcher of the UI thread. You can omit the dispatcher when you construct the type on the
UI thread.
Bind a device list
NearbyDeviceCollection handles DeviceChanged, applies every change on the dispatcher that you
give it, and raises INotifyCollectionChanged. Expose one from your view model:
public sealed class DevicesViewModel : IDisposable
{
public NearbyDeviceCollection Devices { get; }
public DevicesViewModel(INearbyConnectivity nearby, IDispatcher dispatcher)
=> Devices = new NearbyDeviceCollection(nearby, dispatcher);
public void Dispose() => Devices.Dispose();
}
Then bind a CollectionView directly to the collection. NearbyDevice exposes
Presence.DisplayName and Status, so a row needs no converter to show each device:
<CollectionView ItemsSource="{Binding Devices}"
AutomationId="DeviceList">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="nearby:NearbyDevice">
<Grid Padding="12" ColumnDefinitions="*,Auto">
<Label Text="{Binding Presence.DisplayName}" />
<Label Grid.Column="1" Text="{Binding Status}" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
x:DataType needs the namespace on the page's root element:
xmlns:nearby="clr-namespace:Plugin.Maui.NearbyConnectivity;assembly=Plugin.Maui.NearbyConnectivity"
Count raises PropertyChanged, so {Binding Devices.Count} stays current.
Read Count and the indexer only on the dispatcher's thread.
If your filter throws, the collection keeps the devices that it has and stops updating.
IsFaulted becomes true, and Exception holds the exception. The collection also logs the
exception through the logger of the plugin's session. If you pass your own INearbyConnectivity,
such as a decorator, the collection does not log the exception. The collection throws nothing on
the UI thread.
To show only some devices, pass a filter. NearbyChat registers one collection of connected
devices as a singleton in
MauiProgram.cs:
builder.Services.AddSingleton(services => new NearbyDeviceCollection(
services.GetRequiredService<INearbyConnectivity>(),
services.GetRequiredService<IDispatcher>(),
static device => device.Status is NearbyDeviceStatus.Connected));
Do not bind nearby.Devices directly. It is a read-only snapshot list that raises no change
notification, so the view renders one time and never updates.
Bind a status line
NearbySessionState is a bindable view of the session's state. Construct it with the same
dispatcher as the device collection. Its owner disposes it.
// Bindings: Status.IsAdvertising, Status.IsDiscovering, Status.IsReady,
// Status.Availability, Status.DiscoveryStop.Reason, Status.DiscoveryStop.Error, and the
// same pair on Status.AdvertisingStop
public NearbySessionState Status { get; }
public StatusViewModel(INearbyConnectivity nearby, IDispatcher dispatcher)
=> Status = new NearbySessionState(nearby, dispatcher);
public void Dispose() => Status.Dispose();
NearbySessionState merges two sources. AvailabilityChanged reports radios and permissions.
Advertiser.Changed and Discoverer.Changed report a fault that stopped advertising or discovery.
Availability alone misses one case. A platform fault reaches only the activity's LastStop, so a
label bound to availability shows "Ready" while discovery is stopped.
Project your own rows
If a row has its own commands or state, project the devices onto your own row type. That state
belongs to the page, not to the library. A row must stay the same object while its device changes
status. A NearbyDevice cannot be that object, because each change replaces the immutable snapshot
with a new one.
- Subscribe to
DeviceChanged, and dispatch each change to the UI thread. - Then read
nearby.Devices, and apply each device asAdded. Because you subscribed first, no change is lost between the two steps. Each change has the device'sIdas its key, so a change that you apply twice does no harm. - Keep an
ObservableCollection<TRow>and a dictionary keyed bydevice.Id. - For a device that you have not seen, project a row and add it.
- For a device that you already have, give the new snapshot to the existing row. Do not create the row again.
- On
Removed, or when a device no longer passes your filter, remove the row. - Unsubscribe when the page closes.
As an alternative, start from a NearbyDeviceCollection and project its CollectionChanged
events. The collection does steps 1 and 2 for you.
samples/NearbyChat/ViewModels/DeviceRows.cs
is a full example. It includes rows that stay on screen to play an exit after the session removes
them.
Next
- Accept and make connections for the methods that a row's commands call.
- Samples for both sample apps.