Проблема с однократным вызовом 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 на механизм событий:
- В Kotlin-модуле импортируйте
DeviceEventManagerModule.RCTDeviceEventEmitter. - При возникновении события вызывайте
reactContext.getJSModule(RCTDeviceEventEmitter.class).emit("eventName", params). - В 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();
}