Events
The full list of events and their exact EventArgs shapes is generated from XML doc comments in the API reference (see Bluetooth.Abstractions.Scanning and Bluetooth.Abstractions.Broadcasting) — this page covers the conventions that apply across all of them and links to the type where each event actually lives, rather than duplicating a per-event listing that would just drift out of sync again.
Event Patterns
General Conventions
All events in Plugin.Bluetooth follow .NET event patterns:
// Standard event pattern
event EventHandler EventName;
event EventHandler<TEventArgs> EventWithData;
// Usage
scanner.Started += (sender, e) =>
{
Console.WriteLine("Scanner started");
};
scanner.DevicesAdded += (sender, e) =>
{
foreach (var device in e.Items)
{
Console.WriteLine($"Found: {device.Name}");
}
};
Thread Safety
Important: Events may be raised on any thread, including background threads. Always marshal to the UI thread when updating UI components:
scanner.DevicesAdded += async (sender, e) =>
{
await MainThread.InvokeOnMainThreadAsync(() =>
{
foreach (var device in e.Items)
{
DeviceList.Add(device); // UI update
}
});
};
Lifecycle Events
State-changing operations follow a consistent pattern:
// Starting -> Started
// Stopping -> Stopped
// Connecting -> Connected
// Disconnecting -> Disconnected
device.Connecting += (s, e) => Console.WriteLine("Connecting...");
device.Connected += (s, e) => Console.WriteLine("Connected!");
Collection Events
Collections provide three levels of change notification:
// Fine-grained: Added/Removed
scanner.DevicesAdded += (s, e) => { /* new devices */ };
scanner.DevicesRemoved += (s, e) => { /* removed devices */ };
// Combined: Changed
scanner.DeviceListChanged += (s, e) =>
{
var added = e.AddedItems;
var removed = e.RemovedItems;
};
Signal strength: no change event, poll instead
There is no RssiChanged-style event exposed on IBluetoothRemoteDevice — RssiChangedEventArgs exists in the codebase but isn't wired to any public event. Poll instead:
int rssi = await device.ReadSignalStrengthAsync();
device.SignalStrengthInDbm holds the last-read value without triggering a new native read; device.SignalStrengthInPercent gives a normalized 0-100 view of the same reading.
Where each event lives
| Area | Type | Key events |
|---|---|---|
| Scanner lifecycle & device list | IBluetoothScanner | Started, Stopped, DevicesAdded, DevicesRemoved, DeviceListChanged |
| Device connection & advertisements | IBluetoothRemoteDevice | Connecting, Connected, Disconnecting, Disconnected, ConnectionStateChanged, UnexpectedDisconnection, AdvertisementReceived, PairingStateChanged, MtuChanged, PhyChanged |
| Service discovery | IBluetoothRemoteDevice | ServiceListChanged |
| Characteristic value changes | IBluetoothRemoteCharacteristic | ValueUpdated |
| Descriptor list changes | IBluetoothRemoteService | DescriptorListChanged |
| Broadcaster lifecycle & local service list | IBluetoothBroadcaster | broadcaster state events, ServiceListChanged |
| Client connections (broadcaster/peripheral role) | IBluetoothBroadcaster | ClientDeviceListChanged, ClientDevicesAdded, ClientDevicesRemoved |
| Local characteristic read/write requests | IBluetoothLocalCharacteristic | ReadRequested, WriteRequested |
| Local descriptor read/write requests | IBluetoothLocalDescriptor | ReadRequested, WriteRequested |
All EventArgs types are under Bluetooth.Abstractions.Scanning.EventArgs (scanning/client side) and Bluetooth.Abstractions.Broadcasting.EventArgs (broadcasting/server side) — see each interface's generated page for the exact event signature and its EventArgs shape.
Best Practices
Event Subscription
// GOOD: Weak event pattern for long-lived objects
WeakEventManager<IBluetoothScanner, DevicesAddedEventArgs>
.AddHandler(scanner, nameof(scanner.DevicesAdded), OnDevicesAdded);
// GOOD: Explicit unsubscribe
scanner.DevicesAdded += OnDevicesAdded;
// Later...
scanner.DevicesAdded -= OnDevicesAdded;
// BAD: Lambda without unsubscribe (potential memory leak)
scanner.DevicesAdded += (s, e) => { /* ... */ };
Error Handling
device.UnexpectedDisconnection += async (s, e) =>
{
try
{
// Reconnection logic
await device.ConnectAsync();
}
catch (Exception ex)
{
// Log and handle gracefully
logger.LogError(ex, "Reconnection failed");
}
};
Performance
// GOOD: Throttle high-frequency events
private DateTime _lastUpdate = DateTime.MinValue;
characteristic.ValueUpdated += (s, e) =>
{
if ((DateTime.Now - _lastUpdate).TotalMilliseconds < 100)
return; // Throttle to 10Hz
_lastUpdate = DateTime.Now;
ProcessValue(e.NewValue);
};
// GOOD: Use debouncing for UI updates
private CancellationTokenSource? _uiUpdateCts;
scanner.DeviceListChanged += async (s, e) =>
{
_uiUpdateCts?.Cancel();
_uiUpdateCts = new CancellationTokenSource();
try
{
await Task.Delay(100, _uiUpdateCts.Token);
await MainThread.InvokeOnMainThreadAsync(() => RefreshUI());
}
catch (OperationCanceledException) { }
};