DEV Community

HarmonyOS
HarmonyOS

Posted on

Bluetooth Listener Callback Triggered Multiple Times

Read the original article:Bluetooth Listener Callback Triggered Multiple Times

Problem Description

When subscribing to Bluetooth event listeners using the .on('XXX') interface (for example, on('stateChange')), the callback may sometimes be triggered multiple times even though the event is triggered only once.

Example: Bluetooth switch state listener.

import { access } from '@kit.ConnectivityKit';

@Entry
@Component
struct StateChangePage {
  // Counter
  @State number: number = 0;

  on() {
    // Subscribe to Bluetooth state change listener
    access.on('stateChange', (callback: access.BluetoothState) => {
      console.info(`${this.number} :Bluetooth State: ${callback}`)
      this.number++
    })
  }

  build() {
    Column() {
      Button('Start Listening').onClick(() => {
        for (let i = 0; i < 2; i++) {
          // Call the listener twice
          this.on()
        }
      })
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Observed Logs:

15:20:08.535   51966-51966   A03D00/com.example.ble/JSAPP    com.example.ble       I     0: Bluetooth State: 2
15:20:08.535   51966-51966   A03D00/com.example.ble/JSAPP    com.example.ble       I     1: Bluetooth State: 2
Enter fullscreen mode Exit fullscreen mode

The callback triggers twice even though the state changes only once.

Background Knowledge

The .on('XXX') interface can be called multiple times to create multiple subscriptions.
When the listener is no longer needed, developers should explicitly call the corresponding .off('XXX') method to cancel the subscription.
For example: off('stateChange').

Troubleshooting Process

Use debugging or logging to verify whether .on('XXX') is being invoked more than once before adding the listener.
If multiple listeners are created, the callback will trigger repeatedly.

Solution

Solution 1:

Call the corresponding .off('XXX') interface when the listener is no longer needed to prevent stacking multiple listeners.

Example: Bluetooth State Change Listener

import { access } from '@kit.ConnectivityKit';

@Entry
@Component
struct StateChangePage {
  @State number: number = 0;

  on() {
    console.info('Start Listening')
    access.on('stateChange', (callback: access.BluetoothState) => {
      console.info(`${this.number} :Bluetooth State: ${callback}`)
      this.number++
    })
  }

  build() {
    Column() {
      Button('Start Listening').onClick(() => {
        this.on()
      })
      Button('Stop Listening').onClick(() => {
        console.info('Stop Listening')
        access.off('stateChange')
      })
      Button('Enable Bluetooth').onClick(() => {
        if (access.getState() === 0) {
          console.info('Enable Bluetooth')
          access.enableBluetooth()
        }
      })
      Button('Disable Bluetooth').onClick(() => {
        if (access.getState() === 2) {
          console.info('Disable Bluetooth')
          access.disableBluetooth()
        }
      })
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Execution Result:

cke_4980.png

Solution 2:

Before calling .on('XXX'), proactively call .off('XXX') once to ensure previously created listeners are cleared.

import { access } from '@kit.ConnectivityKit';

@Entry
@Component
struct StateChangePage {
  @State number: number = 0;

  on() {
    console.info('Start Listening')
    access.on('stateChange', (callback: access.BluetoothState) => {
      console.info(`${this.number} :Bluetooth State: ${callback}`)
      this.number++
    })
  }

  build() {
    Column() {
      Button('Start Listening').onClick(() => {
        // Clear any previous Bluetooth listeners before adding a new one
        access.off('stateChange')
        console.info('Cleared previous listener')
        this.on()
      })
      Button('Enable Bluetooth').onClick(() => {
        if (access.getState() === 0) {
          console.info('Enable Bluetooth')
          access.enableBluetooth()
        }
      })
      Button('Disable Bluetooth').onClick(() => {
        if (access.getState() === 2) {
          console.info('Disable Bluetooth')
          access.disableBluetooth()
        }
      })
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Execution Result:

cke_6430.png

Verification Result

  • Verified that duplicate callbacks stop occurring after applying either solution.
  • .off('stateChange') effectively removes previously created listeners.
  • Repeated creation of .on('XXX') without corresponding .off() confirmed as root cause.
  • Supports API Version 19 Release and above.
  • Requires HarmonyOS 5.1.1 Release SDK or later.
  • Must be compiled and executed in DevEco Studio 5.1.1 Release or later.

Code Check Cleared Screenshot:

cke_7787.png

Related Documents or Links

Written by Arif Emre Ankara

Top comments (0)