From 882c16079dc61b3b144de237ae593586bf1ec6d2 Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Fri, 1 Mar 2024 18:06:53 +0100 Subject: [PATCH] feat: android debounce backup state events to react native Also fixes the numeric representation of backup state timestamps. Apparently doubles are ok, but integers not. Before this fix the RN side would get wrong timestamp like `"lastQueued": -86628891` --- .../main/java/com/reactnativeldk/Helpers.kt | 41 ++++++++++++++++++- .../reactnativeldk/classes/BackupClient.kt | 24 ++++------- 2 files changed, 49 insertions(+), 16 deletions(-) diff --git a/lib/android/src/main/java/com/reactnativeldk/Helpers.kt b/lib/android/src/main/java/com/reactnativeldk/Helpers.kt index b8417b2b..a39b2ad2 100644 --- a/lib/android/src/main/java/com/reactnativeldk/Helpers.kt +++ b/lib/android/src/main/java/com/reactnativeldk/Helpers.kt @@ -1,4 +1,6 @@ package com.reactnativeldk +import android.os.Handler +import android.os.Looper import com.facebook.react.bridge.* import org.json.JSONObject import org.ldk.enums.Currency @@ -8,6 +10,7 @@ import java.io.File import java.io.FileOutputStream import java.net.URL import java.nio.channels.Channels +import java.util.Date fun handleResolve(promise: Promise, res: LdkCallbackResponses) { if (res != LdkCallbackResponses.log_write_success) { @@ -204,6 +207,22 @@ fun WritableMap.putHexString(key: String, bytes: ByteArray?) { } } +/** + * Adds a Date object into the map. + * If the date is not null, it is converted to a double representing the unix timestamp. + * If it's null, a null value is added to the map. + * + * @param key The key under which the date will be stored in the map. + * @param date The date to be stored in the map. Can be null. + */ +fun WritableMap.putDateOrNull(key: String, date: Date?) { + if (date != null) { + putDouble(key, date.time.toDouble()) + } else { + putNull(key) + } +} + fun WritableArray.pushHexString(bytes: ByteArray) { pushString(bytes.hexEncodedString()) } @@ -502,4 +521,24 @@ fun mergeObj(obj1: JSONObject, obj2: HashMap): HashMap } return newObj -} \ No newline at end of file +} + +object UiThreadDebouncer { + private val pending = hashMapOf() + private val handler = Handler(Looper.getMainLooper()) + + /** + * Used to debounce an [action] function call to be executed after a delay on the main thread + * + * @param interval The delay in milliseconds after which the action will be executed. Default value is 250ms. + * @param key The unique identifier for the action to be debounced. If an action with the same key is already pending, it will be cancelled. + * @param action The function to be executed after the interval. + */ + fun debounce(interval: Long = 250, key: String, action: () -> Unit) { + pending[key]?.let { handler.removeCallbacks(it) } + val runnable = Runnable(action) + pending[key] = runnable + + handler.postDelayed(runnable, interval) + } +} diff --git a/lib/android/src/main/java/com/reactnativeldk/classes/BackupClient.kt b/lib/android/src/main/java/com/reactnativeldk/classes/BackupClient.kt index f747270c..ed879893 100644 --- a/lib/android/src/main/java/com/reactnativeldk/classes/BackupClient.kt +++ b/lib/android/src/main/java/com/reactnativeldk/classes/BackupClient.kt @@ -1,10 +1,13 @@ package com.reactnativeldk.classes + import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.WritableMap +import com.reactnativeldk.UiThreadDebouncer import com.reactnativeldk.EventTypes import com.reactnativeldk.LdkEventEmitter import com.reactnativeldk.hexEncodedString import com.reactnativeldk.hexa +import com.reactnativeldk.putDateOrNull import org.json.JSONObject import org.ldk.structs.Result_StrSecp256k1ErrorZ.Result_StrSecp256k1ErrorZ_OK import org.ldk.structs.UtilMethods @@ -61,17 +64,9 @@ data class BackupFileState( val encoded: WritableMap get() { val body = Arguments.createMap() - body.putInt("lastQueued", lastQueued.time.toInt()) - if (lastPersisted != null) { - body.putInt("lastPersisted", lastPersisted!!.time.toInt()) - } else { - body.putNull("lastPersisted") - } - if (lastFailed != null) { - body.putInt("lastFailed", lastFailed!!.time.toInt()) - } else { - body.putNull("lastFailed") - } + body.putDouble("lastQueued", lastQueued.time.toDouble()) + body.putDateOrNull("lastPersisted", lastPersisted) + body.putDateOrNull("lastFailed", lastFailed) if (lastErrorMessage != null) { body.putString("lastErrorMessage", lastErrorMessage) } else { @@ -592,10 +587,9 @@ class BackupClient { body.putMap(key, state.encoded) } - LdkEventEmitter.send( - EventTypes.backup_state_update, - body - ) + UiThreadDebouncer.debounce(interval = 250, key = "backupStateUpdate") { + LdkEventEmitter.send(EventTypes.backup_state_update, body) + } backupStateLock.unlock() }