react-native-gizwits-sdk-v5 1.7.8-beta → 1.7.10-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/build.gradle +3 -0
- package/android/src/main/java/com/gizwits/reactnativegizwitssdkv5/DeviceListEventCoordinator.kt +72 -0
- package/android/src/main/java/com/gizwits/reactnativegizwitssdkv5/RNGizDeviceManagerModule.kt +11 -0
- package/android/src/main/java/com/gizwits/reactnativegizwitssdkv5/RNGizSDKManagerModule.kt +145 -143
- package/android/src/test/java/com/gizwits/reactnativegizwitssdkv5/DeviceListEventCoordinatorTest.kt +110 -0
- package/package.json +1 -1
package/android/build.gradle
CHANGED
package/android/src/main/java/com/gizwits/reactnativegizwitssdkv5/DeviceListEventCoordinator.kt
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
package com.gizwits.reactnativegizwitssdkv5
|
|
2
|
+
|
|
3
|
+
import kotlinx.coroutines.CoroutineScope
|
|
4
|
+
import kotlinx.coroutines.Job
|
|
5
|
+
import kotlinx.coroutines.channels.Channel
|
|
6
|
+
import kotlinx.coroutines.delay
|
|
7
|
+
import kotlinx.coroutines.launch
|
|
8
|
+
|
|
9
|
+
internal class ConflatedEventDispatcher(
|
|
10
|
+
scope: CoroutineScope,
|
|
11
|
+
private val windowMillis: Long,
|
|
12
|
+
private val dispatch: suspend () -> Unit,
|
|
13
|
+
) {
|
|
14
|
+
private val requests = Channel<Unit>(Channel.CONFLATED)
|
|
15
|
+
private val worker = scope.launch {
|
|
16
|
+
for (request in requests) {
|
|
17
|
+
if (windowMillis > 0) {
|
|
18
|
+
delay(windowMillis)
|
|
19
|
+
}
|
|
20
|
+
while (requests.tryReceive().isSuccess) {
|
|
21
|
+
// Keep only the latest-state notification for this window.
|
|
22
|
+
}
|
|
23
|
+
dispatch()
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
init {
|
|
28
|
+
require(windowMillis >= 0) { "windowMillis must not be negative" }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
internal fun request(): Boolean = requests.trySend(Unit).isSuccess
|
|
32
|
+
|
|
33
|
+
internal fun cancel() {
|
|
34
|
+
requests.close()
|
|
35
|
+
worker.cancel()
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
internal class KeyedSubscriptionRegistry<K, V>(
|
|
40
|
+
private val keySelector: (V) -> K,
|
|
41
|
+
private val subscribe: (V) -> Job,
|
|
42
|
+
) {
|
|
43
|
+
private data class Subscription<V>(val value: V, val job: Job)
|
|
44
|
+
|
|
45
|
+
private val subscriptions = linkedMapOf<K, Subscription<V>>()
|
|
46
|
+
|
|
47
|
+
@Synchronized
|
|
48
|
+
internal fun update(values: Collection<V>) {
|
|
49
|
+
val valuesByKey = values.associateBy(keySelector)
|
|
50
|
+
|
|
51
|
+
(subscriptions.keys - valuesByKey.keys).forEach { key ->
|
|
52
|
+
subscriptions.remove(key)?.job?.cancel()
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
valuesByKey.forEach { (key, value) ->
|
|
56
|
+
val existing = subscriptions[key]
|
|
57
|
+
if (existing == null || !existing.job.isActive || existing.value !== value) {
|
|
58
|
+
existing?.job?.cancel()
|
|
59
|
+
subscriptions[key] = Subscription(value, subscribe(value))
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@Synchronized
|
|
65
|
+
internal fun cancelAll() {
|
|
66
|
+
subscriptions.values.forEach { it.job.cancel() }
|
|
67
|
+
subscriptions.clear()
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
@Synchronized
|
|
71
|
+
internal fun keys(): Set<K> = subscriptions.keys.toSet()
|
|
72
|
+
}
|
package/android/src/main/java/com/gizwits/reactnativegizwitssdkv5/RNGizDeviceManagerModule.kt
CHANGED
|
@@ -188,6 +188,17 @@ class RNGizDeviceManagerModule(reactContext: ReactApplicationContext) : ReactCon
|
|
|
188
188
|
|
|
189
189
|
override fun getName() = "RNGizDeviceManagerModule"
|
|
190
190
|
|
|
191
|
+
/**
|
|
192
|
+
* NativeEventEmitter requires these lifecycle methods on Android. Device
|
|
193
|
+
* events are produced by SDK operations, so there is no native listener
|
|
194
|
+
* resource to allocate or release here.
|
|
195
|
+
*/
|
|
196
|
+
@ReactMethod
|
|
197
|
+
fun addListener(eventName: String) = Unit
|
|
198
|
+
|
|
199
|
+
@ReactMethod
|
|
200
|
+
fun removeListeners(count: Int) = Unit
|
|
201
|
+
|
|
191
202
|
override fun onCatalystInstanceDestroy() {
|
|
192
203
|
super.onCatalystInstanceDestroy()
|
|
193
204
|
moduleScope.cancel() // 取消所有协程
|
|
@@ -28,20 +28,18 @@ import com.google.gson.annotations.SerializedName
|
|
|
28
28
|
import kotlinx.coroutines.CoroutineScope
|
|
29
29
|
import kotlinx.coroutines.Dispatchers
|
|
30
30
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
|
31
|
-
import kotlinx.coroutines.
|
|
32
|
-
import kotlinx.coroutines.
|
|
31
|
+
import kotlinx.coroutines.SupervisorJob
|
|
32
|
+
import kotlinx.coroutines.cancel
|
|
33
33
|
import kotlinx.coroutines.flow.Flow
|
|
34
|
-
import kotlinx.coroutines.flow.
|
|
34
|
+
import kotlinx.coroutines.flow.collect
|
|
35
35
|
import kotlinx.coroutines.flow.combine
|
|
36
|
-
import kotlinx.coroutines.flow.
|
|
37
|
-
import kotlinx.coroutines.flow.
|
|
38
|
-
import kotlinx.coroutines.flow.
|
|
39
|
-
import kotlinx.coroutines.flow.flatMapConcat
|
|
40
|
-
import kotlinx.coroutines.flow.last
|
|
41
|
-
import kotlinx.coroutines.flow.launchIn
|
|
36
|
+
import kotlinx.coroutines.flow.distinctUntilChanged
|
|
37
|
+
import kotlinx.coroutines.flow.drop
|
|
38
|
+
import kotlinx.coroutines.flow.filterNotNull
|
|
42
39
|
import kotlinx.coroutines.flow.mapLatest
|
|
43
40
|
import kotlinx.coroutines.flow.take
|
|
44
41
|
import kotlinx.coroutines.launch
|
|
42
|
+
import kotlinx.coroutines.supervisorScope
|
|
45
43
|
import org.json.JSONArray
|
|
46
44
|
import org.json.JSONObject
|
|
47
45
|
|
|
@@ -62,15 +60,24 @@ data class GizFeedback(
|
|
|
62
60
|
|
|
63
61
|
class RNGizSDKManagerModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
|
|
64
62
|
|
|
63
|
+
private val moduleJob = SupervisorJob()
|
|
64
|
+
private val moduleScope = CoroutineScope(moduleJob + Dispatchers.IO)
|
|
65
|
+
@Volatile
|
|
66
|
+
private var latestDeviceList = emptyList<GizDevice>()
|
|
65
67
|
private var mReactContext: ReactContext? = null
|
|
68
|
+
private val deviceListEventDispatcher = ConflatedEventDispatcher(
|
|
69
|
+
scope = moduleScope,
|
|
70
|
+
windowMillis = 500,
|
|
71
|
+
dispatch = ::emitDeviceListChange,
|
|
72
|
+
)
|
|
73
|
+
private val deviceSubscriptions = KeyedSubscriptionRegistry<String, GizDevice>(
|
|
74
|
+
keySelector = GizDevice::id,
|
|
75
|
+
subscribe = ::subscribeToDevice,
|
|
76
|
+
)
|
|
66
77
|
|
|
67
|
-
private var isSubscriptDevices: List<String> = listOf();
|
|
68
78
|
init {
|
|
69
79
|
mReactContext = reactContext
|
|
70
80
|
}
|
|
71
|
-
companion object {
|
|
72
|
-
internal var devices = listOf<GizDevice>()
|
|
73
|
-
}
|
|
74
81
|
enum class EventName(val value: String) {
|
|
75
82
|
DeviceDataListener("DeviceDataListener"),
|
|
76
83
|
DeviceListListener("DeviceListListener"),
|
|
@@ -107,6 +114,18 @@ class RNGizSDKManagerModule(reactContext: ReactApplicationContext) : ReactContex
|
|
|
107
114
|
}
|
|
108
115
|
|
|
109
116
|
override fun getName() = "RNGizSDKManagerModule"
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* NativeEventEmitter requires these lifecycle methods on Android. SDK
|
|
120
|
+
* subscriptions are owned by this module's lifecycle rather than by each JS
|
|
121
|
+
* listener, so the methods intentionally do not start or stop subscriptions.
|
|
122
|
+
*/
|
|
123
|
+
@ReactMethod
|
|
124
|
+
fun addListener(eventName: String) = Unit
|
|
125
|
+
|
|
126
|
+
@ReactMethod
|
|
127
|
+
fun removeListeners(count: Int) = Unit
|
|
128
|
+
|
|
110
129
|
private val deviceListState: Flow<List<GizDevice>> =
|
|
111
130
|
combineLatest(
|
|
112
131
|
GizSDKManager.subscribeBoundDeviceList(),
|
|
@@ -120,7 +139,7 @@ class RNGizSDKManagerModule(reactContext: ReactApplicationContext) : ReactContex
|
|
|
120
139
|
|
|
121
140
|
override fun initialize() {
|
|
122
141
|
super.initialize()
|
|
123
|
-
|
|
142
|
+
moduleScope.launch {
|
|
124
143
|
GizSDKManager.subscribeBindEvent().collect {
|
|
125
144
|
val data = JSONObject()
|
|
126
145
|
data.put("did", it.first)
|
|
@@ -129,159 +148,141 @@ class RNGizSDKManagerModule(reactContext: ReactApplicationContext) : ReactContex
|
|
|
129
148
|
}
|
|
130
149
|
}
|
|
131
150
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
if (!isSubscriptDevices.contains(dev.mac)) {
|
|
139
|
-
CoroutineScope(Dispatchers.IO).launch {
|
|
140
|
-
async {
|
|
141
|
-
dev.bleCapability.subscribeModuleProfile().debounce(500).collect {
|
|
142
|
-
if (it != null) {
|
|
143
|
-
emitDeviceListChange()
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
async {
|
|
148
|
-
combine(
|
|
149
|
-
dev.bleCapability.subscribeStatus(),
|
|
150
|
-
dev.bleCapability.subscribeIsLogin(),
|
|
151
|
-
) { status, isLogin ->
|
|
152
|
-
Pair(status, isLogin)
|
|
153
|
-
}.collect {
|
|
154
|
-
val data = JSONObject()
|
|
155
|
-
data.put("state", it.first.toInt())
|
|
156
|
-
data.put("isLogin", it.second)
|
|
157
|
-
data.put("type", "BLE")
|
|
158
|
-
data.put("device", dev.toJsonObject())
|
|
159
|
-
sendEvent(EventName.DeviceStateListener.name, GizRNCallbackManager.jsonObject2WriteableMap(data))
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
async {
|
|
163
|
-
dev.bleCapability.subscribeDp().collect{
|
|
164
|
-
val data = JSONObject()
|
|
165
|
-
data.put("data", JSONObject(it.toString()))
|
|
166
|
-
data.put("type", "BLE")
|
|
167
|
-
data.put("device", dev.toJsonObject())
|
|
168
|
-
sendEvent(EventName.DeviceDataListener.name, GizRNCallbackManager.jsonObject2WriteableMap(data))
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
async {
|
|
172
|
-
dev.lanCapability.subscribeModuleProfile().debounce(500).collectLatest{
|
|
173
|
-
if (it != null) {
|
|
174
|
-
emitDeviceListChange()
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
async {
|
|
180
|
-
combineLatest(
|
|
181
|
-
dev.lanCapability.subscribeStatus(),
|
|
182
|
-
dev.lanCapability.subscribeIsLogin(),
|
|
183
|
-
) { status, isLogin ->
|
|
184
|
-
Pair(status, isLogin)
|
|
185
|
-
}.collectLatest {
|
|
186
|
-
val data = JSONObject()
|
|
187
|
-
data.put("state", it.first.toInt())
|
|
188
|
-
data.put("isLogin", it.second)
|
|
189
|
-
data.put("type", "LAN")
|
|
190
|
-
data.put("device", dev.toJsonObject())
|
|
191
|
-
sendEvent(EventName.DeviceStateListener.name, GizRNCallbackManager.jsonObject2WriteableMap(data))
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
async {
|
|
196
|
-
dev.lanCapability.subscribeDp().collect{
|
|
197
|
-
val data = JSONObject()
|
|
198
|
-
data.put("data", JSONObject(it.toString()))
|
|
199
|
-
data.put("type", "LAN")
|
|
200
|
-
data.put("device", dev.toJsonObject())
|
|
201
|
-
sendEvent(EventName.DeviceDataListener.name, GizRNCallbackManager.jsonObject2WriteableMap(data))
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
async {
|
|
206
|
-
combineLatest(
|
|
207
|
-
dev.mqttCapability.subscribeStatus(),
|
|
208
|
-
dev.mqttCapability.subscribeIsLogin(),
|
|
209
|
-
) { status, isLogin ->
|
|
210
|
-
Pair(status, isLogin)
|
|
211
|
-
}.collectLatest {
|
|
212
|
-
val data = JSONObject()
|
|
213
|
-
data.put("state", it.first.toInt())
|
|
214
|
-
data.put("isLogin", it.second)
|
|
215
|
-
data.put("type", "MQTT")
|
|
216
|
-
data.put("device", dev.toJsonObject())
|
|
217
|
-
sendEvent(EventName.DeviceStateListener.name, GizRNCallbackManager.jsonObject2WriteableMap(data))
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// 不会主动发生改表
|
|
222
|
-
// async {
|
|
223
|
-
// dev.mqttCapability.subscribeModuleProfile().debounce(500).collectLatest{
|
|
224
|
-
// if (it != null) {
|
|
225
|
-
// emitDeviceListChange()
|
|
226
|
-
// }
|
|
227
|
-
// }
|
|
228
|
-
// }
|
|
229
|
-
|
|
230
|
-
async {
|
|
231
|
-
dev.mqttCapability.subscribeDp().collect{
|
|
232
|
-
val data = JSONObject()
|
|
233
|
-
data.put("data", JSONObject(it.toString()))
|
|
234
|
-
data.put("type", "MQTT")
|
|
235
|
-
data.put("device", dev.toJsonObject())
|
|
236
|
-
sendEvent(EventName.DeviceDataListener.name, GizRNCallbackManager.jsonObject2WriteableMap(data))
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
}
|
|
151
|
+
moduleScope.launch {
|
|
152
|
+
deviceListState
|
|
153
|
+
.distinctUntilChanged { previous, current ->
|
|
154
|
+
previous.size == current.size && previous.indices.all { index ->
|
|
155
|
+
previous[index].id == current[index].id && previous[index] === current[index]
|
|
240
156
|
}
|
|
241
157
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
*/
|
|
247
|
-
isSubscriptDevices = deviceList.map{
|
|
248
|
-
it.mac
|
|
158
|
+
.collect { deviceList ->
|
|
159
|
+
latestDeviceList = deviceList
|
|
160
|
+
deviceSubscriptions.update(deviceList)
|
|
161
|
+
requestDeviceListChange()
|
|
249
162
|
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
250
165
|
|
|
251
|
-
|
|
166
|
+
private fun subscribeToDevice(device: GizDevice) = moduleScope.launch {
|
|
167
|
+
supervisorScope {
|
|
168
|
+
launch {
|
|
169
|
+
device.bleCapability.subscribeModuleProfile()
|
|
170
|
+
.drop(1)
|
|
171
|
+
.filterNotNull()
|
|
172
|
+
.collect { requestDeviceListChange() }
|
|
252
173
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
174
|
+
launch {
|
|
175
|
+
combine(
|
|
176
|
+
device.bleCapability.subscribeStatus(),
|
|
177
|
+
device.bleCapability.subscribeIsLogin(),
|
|
178
|
+
) { status, isLogin -> Pair(status, isLogin) }
|
|
179
|
+
.collect { (status, isLogin) ->
|
|
180
|
+
val data = JSONObject()
|
|
181
|
+
data.put("state", status.toInt())
|
|
182
|
+
data.put("isLogin", isLogin)
|
|
183
|
+
data.put("type", "BLE")
|
|
184
|
+
data.put("device", device.toJsonObject())
|
|
185
|
+
sendEvent(EventName.DeviceStateListener.name, GizRNCallbackManager.jsonObject2WriteableMap(data))
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
launch {
|
|
189
|
+
device.bleCapability.subscribeDp().collect { payload ->
|
|
190
|
+
val data = JSONObject()
|
|
191
|
+
data.put("data", JSONObject(payload.toString()))
|
|
192
|
+
data.put("type", "BLE")
|
|
193
|
+
data.put("device", device.toJsonObject())
|
|
194
|
+
sendEvent(EventName.DeviceDataListener.name, GizRNCallbackManager.jsonObject2WriteableMap(data))
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
launch {
|
|
198
|
+
device.lanCapability.subscribeModuleProfile()
|
|
199
|
+
.drop(1)
|
|
200
|
+
.filterNotNull()
|
|
201
|
+
.collect { requestDeviceListChange() }
|
|
202
|
+
}
|
|
203
|
+
launch {
|
|
204
|
+
combineLatest(
|
|
205
|
+
device.lanCapability.subscribeStatus(),
|
|
206
|
+
device.lanCapability.subscribeIsLogin(),
|
|
207
|
+
) { status, isLogin -> Pair(status, isLogin) }
|
|
208
|
+
.collect { (status, isLogin) ->
|
|
209
|
+
val data = JSONObject()
|
|
210
|
+
data.put("state", status.toInt())
|
|
211
|
+
data.put("isLogin", isLogin)
|
|
212
|
+
data.put("type", "LAN")
|
|
213
|
+
data.put("device", device.toJsonObject())
|
|
214
|
+
sendEvent(EventName.DeviceStateListener.name, GizRNCallbackManager.jsonObject2WriteableMap(data))
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
launch {
|
|
218
|
+
device.lanCapability.subscribeDp().collect { payload ->
|
|
219
|
+
val data = JSONObject()
|
|
220
|
+
data.put("data", JSONObject(payload.toString()))
|
|
221
|
+
data.put("type", "LAN")
|
|
222
|
+
data.put("device", device.toJsonObject())
|
|
223
|
+
sendEvent(EventName.DeviceDataListener.name, GizRNCallbackManager.jsonObject2WriteableMap(data))
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
launch {
|
|
227
|
+
combineLatest(
|
|
228
|
+
device.mqttCapability.subscribeStatus(),
|
|
229
|
+
device.mqttCapability.subscribeIsLogin(),
|
|
230
|
+
) { status, isLogin -> Pair(status, isLogin) }
|
|
231
|
+
.collect { (status, isLogin) ->
|
|
232
|
+
val data = JSONObject()
|
|
233
|
+
data.put("state", status.toInt())
|
|
234
|
+
data.put("isLogin", isLogin)
|
|
235
|
+
data.put("type", "MQTT")
|
|
236
|
+
data.put("device", device.toJsonObject())
|
|
237
|
+
sendEvent(EventName.DeviceStateListener.name, GizRNCallbackManager.jsonObject2WriteableMap(data))
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
launch {
|
|
241
|
+
device.mqttCapability.subscribeDp().collect { payload ->
|
|
242
|
+
val data = JSONObject()
|
|
243
|
+
data.put("data", JSONObject(payload.toString()))
|
|
244
|
+
data.put("type", "MQTT")
|
|
245
|
+
data.put("device", device.toJsonObject())
|
|
246
|
+
sendEvent(EventName.DeviceDataListener.name, GizRNCallbackManager.jsonObject2WriteableMap(data))
|
|
247
|
+
}
|
|
260
248
|
}
|
|
261
249
|
}
|
|
262
250
|
}
|
|
263
251
|
|
|
252
|
+
private fun requestDeviceListChange() {
|
|
253
|
+
deviceListEventDispatcher.request()
|
|
254
|
+
}
|
|
255
|
+
|
|
264
256
|
/**
|
|
265
257
|
* RN的场景下
|
|
266
258
|
* module profile 变更 相当于设备列表变更
|
|
267
259
|
*/
|
|
268
|
-
suspend fun emitDeviceListChange() {
|
|
269
|
-
val deviceList = deviceListState.first()
|
|
260
|
+
private suspend fun emitDeviceListChange() {
|
|
270
261
|
// 缓存变更
|
|
271
262
|
val deviceListJson = JSONArray()
|
|
272
|
-
|
|
263
|
+
latestDeviceList.forEach{ item ->
|
|
273
264
|
deviceListJson.put(item.toJsonObject())
|
|
274
265
|
}
|
|
275
266
|
sendEvent(EventName.DeviceListListener.name, GizRNCallbackManager.jsonArray2WriteableArray(deviceListJson))
|
|
276
267
|
}
|
|
277
268
|
|
|
269
|
+
override fun invalidate() {
|
|
270
|
+
deviceSubscriptions.cancelAll()
|
|
271
|
+
deviceListEventDispatcher.cancel()
|
|
272
|
+
moduleScope.cancel()
|
|
273
|
+
mReactContext = null
|
|
274
|
+
super.invalidate()
|
|
275
|
+
}
|
|
276
|
+
|
|
278
277
|
fun sendEvent(name:String, data: WritableArray?) {
|
|
279
|
-
mReactContext
|
|
278
|
+
val reactContext = mReactContext ?: return
|
|
279
|
+
reactContext
|
|
280
280
|
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
281
281
|
.emit(name, data)
|
|
282
282
|
}
|
|
283
283
|
fun sendEvent(name:String, data: WritableMap?) {
|
|
284
|
-
mReactContext
|
|
284
|
+
val reactContext = mReactContext ?: return
|
|
285
|
+
reactContext
|
|
285
286
|
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
286
287
|
.emit(name, data)
|
|
287
288
|
}
|
|
@@ -326,6 +327,7 @@ class RNGizSDKManagerModule(reactContext: ReactApplicationContext) : ReactContex
|
|
|
326
327
|
// 查询 绑定设备 返回设备列表
|
|
327
328
|
GizSDKManager.queryBoundDevices()
|
|
328
329
|
deviceListState.take(1).collect {
|
|
330
|
+
latestDeviceList = it
|
|
329
331
|
val jsonData = JSONArray()
|
|
330
332
|
it.forEach{device ->
|
|
331
333
|
jsonData.put(device.toJsonObject())
|
|
@@ -337,7 +339,7 @@ class RNGizSDKManagerModule(reactContext: ReactApplicationContext) : ReactContex
|
|
|
337
339
|
writableMap.putInt("error", 0)
|
|
338
340
|
writableMap.putString("message", "")
|
|
339
341
|
result.invoke(null, writableMap)
|
|
340
|
-
|
|
342
|
+
requestDeviceListChange()
|
|
341
343
|
}
|
|
342
344
|
}
|
|
343
345
|
}
|
package/android/src/test/java/com/gizwits/reactnativegizwitssdkv5/DeviceListEventCoordinatorTest.kt
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
package com.gizwits.reactnativegizwitssdkv5
|
|
2
|
+
|
|
3
|
+
import kotlinx.coroutines.Job
|
|
4
|
+
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
|
5
|
+
import kotlinx.coroutines.test.advanceTimeBy
|
|
6
|
+
import kotlinx.coroutines.test.runCurrent
|
|
7
|
+
import kotlinx.coroutines.test.runTest
|
|
8
|
+
import org.junit.Assert.assertEquals
|
|
9
|
+
import org.junit.Assert.assertFalse
|
|
10
|
+
import org.junit.Assert.assertTrue
|
|
11
|
+
import org.junit.Test
|
|
12
|
+
|
|
13
|
+
@OptIn(ExperimentalCoroutinesApi::class)
|
|
14
|
+
class DeviceListEventCoordinatorTest {
|
|
15
|
+
@Test
|
|
16
|
+
fun requestBeforeWorkerStartsIsDelivered() = runTest {
|
|
17
|
+
var dispatchCount = 0
|
|
18
|
+
val dispatcher = ConflatedEventDispatcher(this, 500) { dispatchCount++ }
|
|
19
|
+
|
|
20
|
+
assertTrue(dispatcher.request())
|
|
21
|
+
advanceTimeBy(500)
|
|
22
|
+
runCurrent()
|
|
23
|
+
|
|
24
|
+
assertEquals(1, dispatchCount)
|
|
25
|
+
dispatcher.cancel()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@Test
|
|
29
|
+
fun burstRequestsDeliverOnlyTheLatestSnapshot() = runTest {
|
|
30
|
+
var latestSnapshot = 0
|
|
31
|
+
val deliveredSnapshots = mutableListOf<Int>()
|
|
32
|
+
val dispatcher = ConflatedEventDispatcher(this, 500) {
|
|
33
|
+
deliveredSnapshots += latestSnapshot
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
dispatcher.request()
|
|
37
|
+
runCurrent()
|
|
38
|
+
repeat(5) { value ->
|
|
39
|
+
latestSnapshot = value + 1
|
|
40
|
+
advanceTimeBy(75)
|
|
41
|
+
dispatcher.request()
|
|
42
|
+
}
|
|
43
|
+
advanceTimeBy(125)
|
|
44
|
+
runCurrent()
|
|
45
|
+
|
|
46
|
+
assertEquals(listOf(5), deliveredSnapshots)
|
|
47
|
+
dispatcher.cancel()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
@Test
|
|
51
|
+
fun continuousRequestsStillDispatchAtEveryWindow() = runTest {
|
|
52
|
+
var dispatchCount = 0
|
|
53
|
+
val dispatcher = ConflatedEventDispatcher(this, 500) { dispatchCount++ }
|
|
54
|
+
|
|
55
|
+
dispatcher.request()
|
|
56
|
+
runCurrent()
|
|
57
|
+
repeat(15) {
|
|
58
|
+
advanceTimeBy(100)
|
|
59
|
+
dispatcher.request()
|
|
60
|
+
runCurrent()
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
assertTrue(dispatchCount >= 2)
|
|
64
|
+
dispatcher.cancel()
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
@Test
|
|
68
|
+
fun registryCancelsRemovedDevicesWithoutDuplicatingRetainedSubscriptions() {
|
|
69
|
+
val createdJobs = mutableMapOf<String, MutableList<Job>>()
|
|
70
|
+
val registry = KeyedSubscriptionRegistry<String, String>(
|
|
71
|
+
keySelector = { it },
|
|
72
|
+
subscribe = { key -> Job().also { createdJobs.getOrPut(key) { mutableListOf() }.add(it) } },
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
registry.update(listOf("a", "b"))
|
|
76
|
+
val firstAJob = createdJobs.getValue("a").single()
|
|
77
|
+
val firstBJob = createdJobs.getValue("b").single()
|
|
78
|
+
registry.update(listOf("b"))
|
|
79
|
+
registry.update(listOf("a", "b"))
|
|
80
|
+
|
|
81
|
+
assertFalse(firstAJob.isActive)
|
|
82
|
+
assertTrue(firstBJob.isActive)
|
|
83
|
+
assertEquals(2, createdJobs.getValue("a").size)
|
|
84
|
+
assertEquals(1, createdJobs.getValue("b").size)
|
|
85
|
+
assertEquals(setOf("a", "b"), registry.keys())
|
|
86
|
+
|
|
87
|
+
registry.cancelAll()
|
|
88
|
+
assertTrue(createdJobs.values.flatten().none { it.isActive })
|
|
89
|
+
assertTrue(registry.keys().isEmpty())
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
@Test
|
|
93
|
+
fun registryReplacesSubscriptionWhenValueInstanceChangesForTheSameKey() {
|
|
94
|
+
data class Device(val id: String)
|
|
95
|
+
|
|
96
|
+
val jobs = mutableListOf<Job>()
|
|
97
|
+
val registry = KeyedSubscriptionRegistry<String, Device>(
|
|
98
|
+
keySelector = Device::id,
|
|
99
|
+
subscribe = { Job().also(jobs::add) },
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
registry.update(listOf(Device("a")))
|
|
103
|
+
registry.update(listOf(Device("a")))
|
|
104
|
+
|
|
105
|
+
assertEquals(2, jobs.size)
|
|
106
|
+
assertFalse(jobs.first().isActive)
|
|
107
|
+
assertTrue(jobs.last().isActive)
|
|
108
|
+
registry.cancelAll()
|
|
109
|
+
}
|
|
110
|
+
}
|