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 IBluetoothRemoteDeviceRssiChangedEventArgs 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) { }
};

See Also