Why do I get an exception saying there are registered CBCentralManager event handlers when there are not?

DonR 26 Reputation points
2021-04-01T01:12:22.987+00:00

I am working on a Xamarin.Forms (version 4.8.0.1687) cross-platform app that will need to be able to connect to other instances of itself using Bluetooth LE. I am using the shared interfaces/platform-specific implementations pattern for BLE. On the iOS side I have come to a puzzling error: In instantiating my class (ActingCentral) that includes a CBCentralManager member, when I assign a callback delegate to that member, the app crashes and throws a System.Reflection.TargetInvocationException. Examining the InnerException I found it was a System.InvalidOperationException with the message Event registration is overwriting existing delegate. Either just use events or your own delegate: MyProject.iOS.ManagerDelegate CoreBluetooth.CBCentralManager+_CBCentralManagerDelegate.

At first I imagined I must have absent-mindedly included an event handler somewhere that I was just forgetting or missing, but when I stripped the class down to the barest esssentials that still satisfy my interface, which is very simple indeed, it still triggered the exception. I double-checked that my CBCentralManagerDelegate-derived delegate class is overriding all the abstract methods, even the deprecated RetrievedConnectedPeripherals, just to make sure an unimplemented method isn't the problem. (I've tried it with and without an implementation of RetrievedConnectedPeripherals.) At this point I'm pretty sure the problem is something beyond my rather limited understanding.

For completeness I tried removing the delegate assignment and assigning event handlers explicitly, and that does work, so I have a fall-back if needed; but I find the delegate class pattern to be a lot cleaner and easier to read and maintain, so I'm not giving up quite yet.

Here is the stripped-down class that includes the CBCentralManager instance:

public class ActingCentral : IActingCentral
{
    public bool IsConnected { get { return true; } }

    private readonly CBCentralManager manager;

    public ActingCentral( )
    {
        this.manager = new CBCentralManager();
        this.manager.Delegate = new ManagerDelegate(); // Exception thrown here
    }

    public async void Connect(int index)
    {
        await Task.Delay(100);
    }

    public async Task<List<IPeripheral>> ScanForService(string serviceUuid)
    {
        await Task.Delay(100);
        return new List<IPeripheral>();
    }
}

This is the stripped-down delegate class (I've tried it with and without base calls; no difference either way):

public class ManagerDelegate : CBCentralManagerDelegate
{
    public override void ConnectedPeripheral(CBCentralManager central, CBPeripheral peripheral)
    {
    }

    public override void DisconnectedPeripheral(CBCentralManager central, CBPeripheral peripheral, NSError error)
    {
    }

    public override void DiscoveredPeripheral(CBCentralManager central, CBPeripheral peripheral, NSDictionary advertisementData, NSNumber RSSI)
    {
    }

    public override void FailedToConnectPeripheral(CBCentralManager central, CBPeripheral peripheral, NSError error)
    {
    }

    public override void RetrievedConnectedPeripherals(CBCentralManager central, CBPeripheral[] peripherals)
    {
    }

    public override void RetrievedPeripherals(CBCentralManager central, CBPeripheral[] peripherals)
    {
    }

    public override void UpdatedState(CBCentralManager central)
    {
    }

    public override void WillRestoreState(CBCentralManager central, NSDictionary dict)
    {
    }
}

Can anyone point me toward understanding why this exception is being thrown?

Xamarin
Xamarin
A Microsoft open-source app platform for building Android and iOS apps with .NET and C#.
5,380 questions
0 comments No comments
{count} votes

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.