Проблема с однократным вызовом callback в React Native модуле

    Я разрабатываю Android-приложение на React Native, которое отображает данные, получаемые от внешнего устройства. Взаимодействие с устройством осуществляется через SDK, для которого необходимо определить класс с методами-обработчиками событий. Приведённый ниже код работает, но колбек в Kotlin вызывается только один раз. Как правильно передать методы из JavaScript, чтобы они выполнялись при каждом возникновении события?

    Исходная реализация

    JavaScript (JS)

    function init() {
      Device.onStatusChange((status) => {
        console.log(`Status changed to: ${status}`);
      });
      Device.init();
    }
    

    Kotlin

    import com.facebook.react.bridge.Callback
    
    class Device {
      var statusCallback: Callback? = null
      var statusChagne // Опечатка, вероятно, не используется
    
      @ReactMethod
      init() {
        Hub.getInstance().addListener(listener())
        Hub.getInstance().connect()
      }
    
      fun listener(): DeviceListener {
        return object: AbstractDeviceListener() {
          override public fun onStatusChange(status: String, timestamp: Long) {
            statusCallback?.invoke(status)
          }
          // ...
        }
      }
    
      @ReactMethod
      fun onStatusChange(callback: Callback) {
        statusCallback = callback;
      }
    }
    

    Проблема

    Колбек statusCallback вызывается только один раз, хотя событие onStatusChange может возникать многократно. Это связано с тем, что Callback в React Native предназначен для однократного вызова и не поддерживает повторное использование.

    Решение

    Для многократного вызова события из нативного модуля в JavaScript следует использовать:

    • EventEmitter - отправлять события из Kotlin в JS через ReactContext.
    • Promise - если необходимо передать результат один раз.
    • WritableMap - для передачи сложных данных вместе с событием.

    Рекомендуемый подход - заменить Callback на механизм событий:

    1. В Kotlin-модуле импортируйте DeviceEventManagerModule.RCTDeviceEventEmitter.
    2. При возникновении события вызывайте reactContext.getJSModule(RCTDeviceEventEmitter.class).emit("eventName", params).
    3. В JavaScript подписывайтесь на событие через DeviceEventEmitter.addListener или NativeEventEmitter.

    Пример исправленного Kotlin-кода:

    import com.facebook.react.bridge.ReactApplicationContext
    import com.facebook.react.bridge.ReactContextBaseJavaModule
    import com.facebook.react.bridge.ReactMethod
    import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter
    
    class DeviceModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
      override fun getName() = "Device"
    
      @ReactMethod
      fun init() {
        Hub.getInstance().addListener(listener())
        Hub.getInstance().connect()
      }
    
      private fun listener(): DeviceListener {
        return object : AbstractDeviceListener() {
          override fun onStatusChange(status: String, timestamp: Long) {
            val params = Arguments.createMap()
            params.putString("status", status)
            params.putDouble("timestamp", timestamp.toDouble())
            reactApplicationContext
              .getJSModule(RCTDeviceEventEmitter::class.java)
              .emit("onStatusChange", params)
          }
        }
      }
    }
    

    И в JavaScript:

    import { NativeEventEmitter, NativeModules } from 'react-native';
    
    const { Device } = NativeModules;
    const eventEmitter = new NativeEventEmitter(Device);
    
    function init() {
      const subscription = eventEmitter.addListener('onStatusChange', (event) => {
        console.log(`Status changed to: ${event.status} at ${event.timestamp}`);
      });
      Device.init();
      // Не забудьте отписаться при необходимости: subscription.remove();
    }