@fto-consult/expo-ui 6.37.3 → 6.37.5

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.
Files changed (114) hide show
  1. package/App.js +0 -1
  2. package/node_modules/.package-lock.json +26218 -26188
  3. package/node_modules/@fto-consult/common/babel.config.alias.js +18 -1
  4. package/node_modules/@fto-consult/common/package.json +1 -1
  5. package/node_modules/@react-native-async-storage/async-storage/LICENSE +21 -0
  6. package/node_modules/@react-native-async-storage/async-storage/README.md +27 -0
  7. package/node_modules/@react-native-async-storage/async-storage/RNCAsyncStorage.podspec +19 -0
  8. package/node_modules/@react-native-async-storage/async-storage/android/build.gradle +142 -0
  9. package/node_modules/@react-native-async-storage/async-storage/android/src/main/AndroidManifest.xml +6 -0
  10. package/node_modules/@react-native-async-storage/async-storage/android/src/main/java/com/reactnativecommunity/asyncstorage/AsyncLocalStorageUtil.java +178 -0
  11. package/node_modules/@react-native-async-storage/async-storage/android/src/main/java/com/reactnativecommunity/asyncstorage/AsyncStorageErrorUtil.java +45 -0
  12. package/node_modules/@react-native-async-storage/async-storage/android/src/main/java/com/reactnativecommunity/asyncstorage/AsyncStorageExpoMigration.java +154 -0
  13. package/node_modules/@react-native-async-storage/async-storage/android/src/main/java/com/reactnativecommunity/asyncstorage/AsyncStorageModule.java +424 -0
  14. package/node_modules/@react-native-async-storage/async-storage/android/src/main/java/com/reactnativecommunity/asyncstorage/AsyncStoragePackage.java +58 -0
  15. package/node_modules/@react-native-async-storage/async-storage/android/src/main/java/com/reactnativecommunity/asyncstorage/ReactDatabaseSupplier.java +163 -0
  16. package/node_modules/@react-native-async-storage/async-storage/android/src/main/java/com/reactnativecommunity/asyncstorage/SerialExecutor.java +40 -0
  17. package/node_modules/@react-native-async-storage/async-storage/android/src/main/java/com/reactnativecommunity/asyncstorage/next/ArgumentHelpers.kt +86 -0
  18. package/node_modules/@react-native-async-storage/async-storage/android/src/main/java/com/reactnativecommunity/asyncstorage/next/ErrorHelpers.kt +39 -0
  19. package/node_modules/@react-native-async-storage/async-storage/android/src/main/java/com/reactnativecommunity/asyncstorage/next/StorageModule.kt +90 -0
  20. package/node_modules/@react-native-async-storage/async-storage/android/src/main/java/com/reactnativecommunity/asyncstorage/next/StorageSupplier.kt +161 -0
  21. package/node_modules/@react-native-async-storage/async-storage/android/src/test/java/com/reactnativecommunity/asyncstorage/next/ArgumentHelpersTest.kt +93 -0
  22. package/node_modules/@react-native-async-storage/async-storage/android/src/test/java/com/reactnativecommunity/asyncstorage/next/StorageTest.kt +141 -0
  23. package/node_modules/@react-native-async-storage/async-storage/android/testresults.gradle +38 -0
  24. package/node_modules/@react-native-async-storage/async-storage/ios/RNCAsyncStorage.h +51 -0
  25. package/node_modules/@react-native-async-storage/async-storage/ios/RNCAsyncStorage.m +898 -0
  26. package/node_modules/@react-native-async-storage/async-storage/ios/RNCAsyncStorage.xcodeproj/project.pbxproj +283 -0
  27. package/node_modules/@react-native-async-storage/async-storage/ios/RNCAsyncStorageDelegate.h +73 -0
  28. package/node_modules/@react-native-async-storage/async-storage/jest/async-storage-mock.d.ts +9 -0
  29. package/node_modules/@react-native-async-storage/async-storage/jest/async-storage-mock.js +109 -0
  30. package/node_modules/@react-native-async-storage/async-storage/lib/commonjs/AsyncStorage.js +164 -0
  31. package/node_modules/@react-native-async-storage/async-storage/lib/commonjs/AsyncStorage.js.map +1 -0
  32. package/node_modules/@react-native-async-storage/async-storage/lib/commonjs/AsyncStorage.native.js +366 -0
  33. package/node_modules/@react-native-async-storage/async-storage/lib/commonjs/AsyncStorage.native.js.map +1 -0
  34. package/node_modules/@react-native-async-storage/async-storage/lib/commonjs/RCTAsyncStorage.js +30 -0
  35. package/node_modules/@react-native-async-storage/async-storage/lib/commonjs/RCTAsyncStorage.js.map +1 -0
  36. package/node_modules/@react-native-async-storage/async-storage/lib/commonjs/helpers.js +69 -0
  37. package/node_modules/@react-native-async-storage/async-storage/lib/commonjs/helpers.js.map +1 -0
  38. package/node_modules/@react-native-async-storage/async-storage/lib/commonjs/hooks.js +44 -0
  39. package/node_modules/@react-native-async-storage/async-storage/lib/commonjs/hooks.js.map +1 -0
  40. package/node_modules/@react-native-async-storage/async-storage/lib/commonjs/index.js +22 -0
  41. package/node_modules/@react-native-async-storage/async-storage/lib/commonjs/index.js.map +1 -0
  42. package/node_modules/@react-native-async-storage/async-storage/lib/commonjs/shouldFallbackToLegacyNativeModule.js +39 -0
  43. package/node_modules/@react-native-async-storage/async-storage/lib/commonjs/shouldFallbackToLegacyNativeModule.js.map +1 -0
  44. package/node_modules/@react-native-async-storage/async-storage/lib/commonjs/types.js +2 -0
  45. package/node_modules/@react-native-async-storage/async-storage/lib/commonjs/types.js.map +1 -0
  46. package/node_modules/@react-native-async-storage/async-storage/lib/module/AsyncStorage.js +153 -0
  47. package/node_modules/@react-native-async-storage/async-storage/lib/module/AsyncStorage.js.map +1 -0
  48. package/node_modules/@react-native-async-storage/async-storage/lib/module/AsyncStorage.native.js +348 -0
  49. package/node_modules/@react-native-async-storage/async-storage/lib/module/AsyncStorage.native.js.map +1 -0
  50. package/node_modules/@react-native-async-storage/async-storage/lib/module/RCTAsyncStorage.js +20 -0
  51. package/node_modules/@react-native-async-storage/async-storage/lib/module/RCTAsyncStorage.js.map +1 -0
  52. package/node_modules/@react-native-async-storage/async-storage/lib/module/helpers.js +56 -0
  53. package/node_modules/@react-native-async-storage/async-storage/lib/module/helpers.js.map +1 -0
  54. package/node_modules/@react-native-async-storage/async-storage/lib/module/hooks.js +34 -0
  55. package/node_modules/@react-native-async-storage/async-storage/lib/module/hooks.js.map +1 -0
  56. package/node_modules/@react-native-async-storage/async-storage/lib/module/index.js +4 -0
  57. package/node_modules/@react-native-async-storage/async-storage/lib/module/index.js.map +1 -0
  58. package/node_modules/@react-native-async-storage/async-storage/lib/module/shouldFallbackToLegacyNativeModule.js +31 -0
  59. package/node_modules/@react-native-async-storage/async-storage/lib/module/shouldFallbackToLegacyNativeModule.js.map +1 -0
  60. package/node_modules/@react-native-async-storage/async-storage/lib/module/types.js +2 -0
  61. package/node_modules/@react-native-async-storage/async-storage/lib/module/types.js.map +1 -0
  62. package/node_modules/@react-native-async-storage/async-storage/lib/typescript/AsyncStorage.d.ts +10 -0
  63. package/node_modules/@react-native-async-storage/async-storage/lib/typescript/AsyncStorage.native.d.ts +16 -0
  64. package/node_modules/@react-native-async-storage/async-storage/lib/typescript/RCTAsyncStorage.d.ts +2 -0
  65. package/node_modules/@react-native-async-storage/async-storage/lib/typescript/helpers.d.ts +5 -0
  66. package/node_modules/@react-native-async-storage/async-storage/lib/typescript/hooks.d.ts +2 -0
  67. package/node_modules/@react-native-async-storage/async-storage/lib/typescript/index.d.ts +4 -0
  68. package/node_modules/@react-native-async-storage/async-storage/lib/typescript/shouldFallbackToLegacyNativeModule.d.ts +1 -0
  69. package/node_modules/@react-native-async-storage/async-storage/lib/typescript/types.d.ts +113 -0
  70. package/node_modules/@react-native-async-storage/async-storage/macos/RNCAsyncStorage.xcodeproj/project.pbxproj +385 -0
  71. package/node_modules/@react-native-async-storage/async-storage/macos/RNCAsyncStorage.xcodeproj/xcshareddata/xcschemes/RNCAsyncStorage-macOS.xcscheme +67 -0
  72. package/node_modules/@react-native-async-storage/async-storage/macos/RNCAsyncStorage.xcodeproj/xcshareddata/xcschemes/RNCAsyncStorage.xcscheme +67 -0
  73. package/node_modules/@react-native-async-storage/async-storage/package.json +197 -0
  74. package/node_modules/@react-native-async-storage/async-storage/src/AsyncStorage.native.ts +356 -0
  75. package/node_modules/@react-native-async-storage/async-storage/src/AsyncStorage.ts +173 -0
  76. package/node_modules/@react-native-async-storage/async-storage/src/RCTAsyncStorage.ts +28 -0
  77. package/node_modules/@react-native-async-storage/async-storage/src/helpers.ts +74 -0
  78. package/node_modules/@react-native-async-storage/async-storage/src/hooks.ts +11 -0
  79. package/node_modules/@react-native-async-storage/async-storage/src/index.ts +7 -0
  80. package/node_modules/@react-native-async-storage/async-storage/src/shouldFallbackToLegacyNativeModule.ts +34 -0
  81. package/node_modules/@react-native-async-storage/async-storage/src/types.ts +155 -0
  82. package/node_modules/@react-native-async-storage/async-storage/windows/ReactNativeAsyncStorage/PropertySheet.props +16 -0
  83. package/node_modules/@react-native-async-storage/async-storage/windows/ReactNativeAsyncStorage/ReactNativeAsyncStorage.vcxproj +172 -0
  84. package/node_modules/@react-native-async-storage/async-storage/windows/ReactNativeAsyncStorage/ReactNativeAsyncStorage.vcxproj.filters +34 -0
  85. package/node_modules/@react-native-async-storage/async-storage/windows/ReactNativeAsyncStorage/packages.config +4 -0
  86. package/node_modules/@react-native-async-storage/async-storage/windows/ReactNativeAsyncStorage.sln +172 -0
  87. package/node_modules/@react-native-async-storage/async-storage/windows/ReactNativeAsyncStorage61/PropertySheet.props +16 -0
  88. package/node_modules/@react-native-async-storage/async-storage/windows/ReactNativeAsyncStorage61/ReactNativeAsyncStorage61.vcxproj +157 -0
  89. package/node_modules/@react-native-async-storage/async-storage/windows/ReactNativeAsyncStorage61/ReactNativeAsyncStorage61.vcxproj.filters +34 -0
  90. package/node_modules/@react-native-async-storage/async-storage/windows/ReactNativeAsyncStorage61/packages.config +4 -0
  91. package/node_modules/@react-native-async-storage/async-storage/windows/ReactNativeAsyncStorage61.sln +195 -0
  92. package/node_modules/@react-native-async-storage/async-storage/windows/ReactNativeAsyncStorage62.sln +192 -0
  93. package/node_modules/@react-native-async-storage/async-storage/windows/code/DBStorage.cpp +599 -0
  94. package/node_modules/@react-native-async-storage/async-storage/windows/code/DBStorage.h +162 -0
  95. package/node_modules/@react-native-async-storage/async-storage/windows/code/RNCAsyncStorage.h +118 -0
  96. package/node_modules/@react-native-async-storage/async-storage/windows/code/ReactNativeAsyncStorage.def +3 -0
  97. package/node_modules/@react-native-async-storage/async-storage/windows/code/ReactPackageProvider.cpp +20 -0
  98. package/node_modules/@react-native-async-storage/async-storage/windows/code/ReactPackageProvider.h +23 -0
  99. package/node_modules/@react-native-async-storage/async-storage/windows/code/ReactPackageProvider.idl +7 -0
  100. package/node_modules/@react-native-async-storage/async-storage/windows/code/pch.cpp +3 -0
  101. package/node_modules/@react-native-async-storage/async-storage/windows/code/pch.h +15 -0
  102. package/node_modules/merge-options/index.d.ts +2 -0
  103. package/node_modules/merge-options/index.js +171 -0
  104. package/node_modules/merge-options/index.mjs +8 -0
  105. package/node_modules/merge-options/license +21 -0
  106. package/node_modules/merge-options/node_modules/is-plain-obj/index.d.ts +29 -0
  107. package/node_modules/merge-options/node_modules/is-plain-obj/index.js +10 -0
  108. package/node_modules/merge-options/node_modules/is-plain-obj/license +9 -0
  109. package/node_modules/merge-options/node_modules/is-plain-obj/package.json +38 -0
  110. package/node_modules/merge-options/node_modules/is-plain-obj/readme.md +54 -0
  111. package/node_modules/merge-options/package.json +59 -0
  112. package/node_modules/merge-options/readme.md +130 -0
  113. package/package.json +131 -130
  114. package/src/components/Chart/appexChart/appexChart.html +23 -23
@@ -0,0 +1,86 @@
1
+ package com.reactnativecommunity.asyncstorage.next
2
+
3
+ import com.facebook.react.bridge.Arguments
4
+ import com.facebook.react.bridge.ReadableArray
5
+ import org.json.JSONException
6
+ import org.json.JSONObject
7
+
8
+ fun ReadableArray.toEntryList(): List<Entry> {
9
+ val list = mutableListOf<Entry>()
10
+ for (keyValue in this.toArrayList()) {
11
+ if (keyValue !is ArrayList<*> || keyValue.size != 2) {
12
+ throw AsyncStorageError.invalidKeyValueFormat()
13
+ }
14
+ val key = keyValue[0]
15
+ val value = keyValue[1]
16
+
17
+ if (key !is String) {
18
+ when (key) {
19
+ null -> throw AsyncStorageError.keyIsNull()
20
+ else -> throw AsyncStorageError.keyNotString()
21
+ }
22
+ }
23
+
24
+ if (value !is String) {
25
+ throw AsyncStorageError.valueNotString(key)
26
+ }
27
+
28
+ list.add(Entry(key, value))
29
+ }
30
+ return list
31
+ }
32
+
33
+ fun ReadableArray.toKeyList(): List<String> {
34
+ val list = this.toArrayList()
35
+
36
+ for (item in list) {
37
+ if (item !is String) {
38
+ throw AsyncStorageError.keyNotString()
39
+ }
40
+ }
41
+ return list as List<String>
42
+ }
43
+
44
+ fun List<Entry>.toKeyValueArgument(): ReadableArray {
45
+ val args = Arguments.createArray()
46
+
47
+ for (entry in this) {
48
+ val keyValue = Arguments.createArray()
49
+ keyValue.pushString(entry.key)
50
+ keyValue.pushString(entry.value)
51
+ args.pushArray(keyValue)
52
+ }
53
+
54
+ return args
55
+ }
56
+
57
+ fun String?.isValidJson(): Boolean {
58
+ if (this == null) return false
59
+
60
+ return try {
61
+ JSONObject(this)
62
+ true
63
+ } catch (e: JSONException) {
64
+ false
65
+ }
66
+ }
67
+
68
+ fun JSONObject.mergeWith(newObject: JSONObject): JSONObject {
69
+
70
+ val keys = newObject.keys()
71
+ val mergedObject = JSONObject(this.toString())
72
+
73
+ while (keys.hasNext()) {
74
+ val key = keys.next()
75
+ val curValue = this.optJSONObject(key)
76
+ val newValue = newObject.optJSONObject(key)
77
+
78
+ if (curValue != null && newValue != null) {
79
+ val merged = curValue.mergeWith(newValue)
80
+ mergedObject.put(key, merged)
81
+ } else {
82
+ mergedObject.put(key, newObject.get(key))
83
+ }
84
+ }
85
+ return mergedObject
86
+ }
@@ -0,0 +1,39 @@
1
+ package com.reactnativecommunity.asyncstorage.next
2
+
3
+ import com.facebook.react.bridge.Arguments
4
+ import com.facebook.react.bridge.Callback
5
+ import kotlinx.coroutines.CoroutineExceptionHandler
6
+
7
+ internal fun createExceptionHandler(cb: Callback): CoroutineExceptionHandler {
8
+ return CoroutineExceptionHandler { _, throwable ->
9
+ val error = Arguments.createMap()
10
+ if (throwable !is AsyncStorageError) {
11
+ error.putString(
12
+ "message", "Unexpected AsyncStorage error: ${throwable.localizedMessage}"
13
+ )
14
+ } else {
15
+ error.putString("message", throwable.errorMessage)
16
+ }
17
+
18
+ cb(error)
19
+ }
20
+ }
21
+
22
+ internal class AsyncStorageError private constructor(val errorMessage: String) :
23
+ Throwable(errorMessage) {
24
+
25
+ companion object {
26
+ fun keyIsNull() = AsyncStorageError("Key cannot be null.")
27
+
28
+ fun keyNotString() = AsyncStorageError("Provided key is not string. Only strings are supported as storage key.")
29
+
30
+ fun valueNotString(key: String?): AsyncStorageError {
31
+ val detail = if (key == null) "Provided value" else "Value for key \"$key\""
32
+ return AsyncStorageError("$detail is not a string. Only strings are supported as a value.")
33
+ }
34
+
35
+ fun invalidKeyValueFormat() =
36
+ AsyncStorageError("Invalid key-value format. Expected a list of [key, value] list.")
37
+
38
+ }
39
+ }
@@ -0,0 +1,90 @@
1
+ package com.reactnativecommunity.asyncstorage.next
2
+
3
+ import android.content.Context
4
+ import androidx.annotation.VisibleForTesting
5
+ import com.facebook.react.bridge.Arguments
6
+ import com.facebook.react.bridge.Callback
7
+ import com.facebook.react.bridge.ReactContext
8
+ import com.facebook.react.bridge.ReactContextBaseJavaModule
9
+ import com.facebook.react.bridge.ReactMethod
10
+ import com.facebook.react.bridge.ReadableArray
11
+ import com.reactnativecommunity.asyncstorage.SerialExecutor
12
+ import kotlinx.coroutines.CoroutineName
13
+ import kotlinx.coroutines.CoroutineScope
14
+ import kotlinx.coroutines.Dispatchers
15
+ import kotlinx.coroutines.SupervisorJob
16
+ import kotlinx.coroutines.asExecutor
17
+ import kotlinx.coroutines.launch
18
+
19
+ class StorageModule(reactContext: ReactContext) : ReactContextBaseJavaModule(), CoroutineScope {
20
+ override fun getName() = "RNC_AsyncSQLiteDBStorage"
21
+
22
+ // this executor is not used by the module, but it must exists here due to
23
+ // Detox relying on this implementation detail to run
24
+ @VisibleForTesting
25
+ private val executor = SerialExecutor(Dispatchers.Main.asExecutor())
26
+
27
+ override val coroutineContext =
28
+ Dispatchers.IO + CoroutineName("AsyncStorageScope") + SupervisorJob()
29
+
30
+ private val storage = StorageSupplier.getInstance(reactContext)
31
+
32
+ companion object {
33
+ @JvmStatic
34
+ fun getStorageInstance(ctx: Context): AsyncStorageAccess {
35
+ return StorageSupplier.getInstance(ctx)
36
+ }
37
+ }
38
+
39
+ @ReactMethod
40
+ fun multiGet(keys: ReadableArray, cb: Callback) {
41
+ launch(createExceptionHandler(cb)) {
42
+ val entries = storage.getValues(keys.toKeyList())
43
+ cb(null, entries.toKeyValueArgument())
44
+ }
45
+ }
46
+
47
+ @ReactMethod
48
+ fun multiSet(keyValueArray: ReadableArray, cb: Callback) {
49
+ launch(createExceptionHandler(cb)) {
50
+ val entries = keyValueArray.toEntryList()
51
+ storage.setValues(entries)
52
+ cb(null)
53
+ }
54
+ }
55
+
56
+ @ReactMethod
57
+ fun multiRemove(keys: ReadableArray, cb: Callback) {
58
+ launch(createExceptionHandler(cb)) {
59
+ storage.removeValues(keys.toKeyList())
60
+ cb(null)
61
+ }
62
+ }
63
+
64
+ @ReactMethod
65
+ fun multiMerge(keyValueArray: ReadableArray, cb: Callback) {
66
+ launch(createExceptionHandler(cb)) {
67
+ val entries = keyValueArray.toEntryList()
68
+ storage.mergeValues(entries)
69
+ cb(null)
70
+ }
71
+ }
72
+
73
+ @ReactMethod
74
+ fun getAllKeys(cb: Callback) {
75
+ launch(createExceptionHandler(cb)) {
76
+ val keys = storage.getKeys()
77
+ val result = Arguments.createArray()
78
+ keys.forEach { result.pushString(it) }
79
+ cb.invoke(null, result)
80
+ }
81
+ }
82
+
83
+ @ReactMethod
84
+ fun clear(cb: Callback) {
85
+ launch(createExceptionHandler(cb)) {
86
+ storage.clear()
87
+ cb(null)
88
+ }
89
+ }
90
+ }
@@ -0,0 +1,161 @@
1
+ package com.reactnativecommunity.asyncstorage.next
2
+
3
+ import android.content.Context
4
+ import android.util.Log
5
+ import androidx.room.ColumnInfo
6
+ import androidx.room.Dao
7
+ import androidx.room.Database
8
+ import androidx.room.Entity
9
+ import androidx.room.Insert
10
+ import androidx.room.OnConflictStrategy
11
+ import androidx.room.PrimaryKey
12
+ import androidx.room.Query
13
+ import androidx.room.Room
14
+ import androidx.room.RoomDatabase
15
+ import androidx.room.Transaction
16
+ import androidx.room.migration.Migration
17
+ import androidx.sqlite.db.SupportSQLiteDatabase
18
+ import org.json.JSONObject
19
+
20
+ private const val DATABASE_VERSION = 2
21
+ private const val DATABASE_NAME = "AsyncStorage"
22
+ private const val TABLE_NAME = "Storage"
23
+ private const val COLUMN_KEY = "key"
24
+ private const val COLUMN_VALUE = "value"
25
+
26
+
27
+ @Entity(tableName = TABLE_NAME)
28
+ data class Entry(
29
+ @PrimaryKey @ColumnInfo(name = COLUMN_KEY) val key: String,
30
+ @ColumnInfo(name = COLUMN_VALUE) val value: String?
31
+ )
32
+
33
+ @Dao
34
+ internal interface StorageDao {
35
+
36
+ @Transaction
37
+ @Query("SELECT * FROM $TABLE_NAME WHERE `$COLUMN_KEY` IN (:keys)")
38
+ suspend fun getValues(keys: List<String>): List<Entry>
39
+
40
+ @Transaction
41
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
42
+ suspend fun setValues(entries: List<Entry>)
43
+
44
+ @Transaction
45
+ @Query("DELETE FROM $TABLE_NAME WHERE `$COLUMN_KEY` in (:keys)")
46
+ suspend fun removeValues(keys: List<String>)
47
+
48
+ @Transaction
49
+ suspend fun mergeValues(entries: List<Entry>) {
50
+ val currentDbEntries = getValues(entries.map { it.key })
51
+ val newEntries = mutableListOf<Entry>()
52
+
53
+ entries.forEach { newEntry ->
54
+ val oldEntry = currentDbEntries.find { it.key == newEntry.key }
55
+ if (oldEntry?.value == null) {
56
+ newEntries.add(newEntry)
57
+ } else if (!oldEntry.value.isValidJson() || !newEntry.value.isValidJson()) {
58
+ newEntries.add(newEntry)
59
+ } else {
60
+ val newValue =
61
+ JSONObject(oldEntry.value).mergeWith(JSONObject(newEntry.value)).toString()
62
+ newEntries.add(newEntry.copy(value = newValue))
63
+ }
64
+ }
65
+ setValues(newEntries)
66
+ }
67
+
68
+ @Transaction
69
+ @Query("SELECT `$COLUMN_KEY` FROM $TABLE_NAME")
70
+ suspend fun getKeys(): List<String>
71
+
72
+ @Transaction
73
+ @Query("DELETE FROM $TABLE_NAME")
74
+ suspend fun clear()
75
+ }
76
+
77
+
78
+ /**
79
+ * Previous version of AsyncStorage is violating the SQL standard (based on bug in SQLite),
80
+ * where PrimaryKey ('key' column) should never be null (https://www.sqlite.org/lang_createtable.html#the_primary_key).
81
+ * Because of that, we cannot reuse the old DB, because ROOM is guarded against that case (won't compile).
82
+ *
83
+ * In order to work around this, two steps are necessary:
84
+ * - Room DB pre-population from the old database file (https://developer.android.com/training/data-storage/room/prepopulate#from-asset)
85
+ * - Version migration, so that we can mark 'key' column as NOT-NULL
86
+ *
87
+ * This migration will happens only once, when developer enable this feature (when DB is still not created).
88
+ */
89
+ @Suppress("ClassName")
90
+ private object MIGRATION_TO_NEXT : Migration(1, 2) {
91
+ override fun migrate(database: SupportSQLiteDatabase) {
92
+ val oldTableName = "catalystLocalStorage" // from ReactDatabaseSupplier
93
+ database.execSQL("CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`${COLUMN_KEY}` TEXT NOT NULL, `${COLUMN_VALUE}` TEXT, PRIMARY KEY(`${COLUMN_KEY}`));")
94
+ // even if the old AsyncStorage has checks for not nullable keys
95
+ // make sure we don't copy any, to not fail migration
96
+ database.execSQL("DELETE FROM $oldTableName WHERE `${COLUMN_KEY}` IS NULL")
97
+ database.execSQL(
98
+ """
99
+ INSERT INTO $TABLE_NAME (`${COLUMN_KEY}`, `${COLUMN_VALUE}`)
100
+ SELECT `${COLUMN_KEY}`, `${COLUMN_VALUE}`
101
+ FROM $oldTableName;
102
+ """.trimIndent()
103
+ )
104
+ Log.e("AsyncStorage_Next", "Migration to Next storage completed.")
105
+ }
106
+ }
107
+
108
+ @Database(entities = [Entry::class], version = DATABASE_VERSION, exportSchema = false)
109
+ internal abstract class StorageDb : RoomDatabase() {
110
+ abstract fun storage(): StorageDao
111
+
112
+ companion object {
113
+ private var instance: StorageDb? = null
114
+
115
+ fun getDatabase(context: Context): StorageDb {
116
+ var inst = instance
117
+ if (inst != null) {
118
+ return inst
119
+ }
120
+ synchronized(this) {
121
+ val oldDbFile = context.getDatabasePath("RKStorage")
122
+ val db = Room.databaseBuilder(
123
+ context, StorageDb::class.java, DATABASE_NAME
124
+ )
125
+ if (oldDbFile.exists()) {
126
+ // migrate data from old database, if it exists
127
+ db.createFromFile(oldDbFile).addMigrations(MIGRATION_TO_NEXT)
128
+ }
129
+ inst = db.build()
130
+ instance = inst
131
+ return instance!!
132
+ }
133
+ }
134
+ }
135
+ }
136
+
137
+ interface AsyncStorageAccess {
138
+ suspend fun getValues(keys: List<String>): List<Entry>
139
+ suspend fun setValues(entries: List<Entry>)
140
+ suspend fun removeValues(keys: List<String>)
141
+ suspend fun getKeys(): List<String>
142
+ suspend fun clear()
143
+ suspend fun mergeValues(entries: List<Entry>)
144
+ }
145
+
146
+ class StorageSupplier internal constructor(db: StorageDb) : AsyncStorageAccess {
147
+ companion object {
148
+ fun getInstance(ctx: Context): AsyncStorageAccess {
149
+ return StorageSupplier(StorageDb.getDatabase(ctx))
150
+ }
151
+ }
152
+
153
+ private val access = db.storage()
154
+
155
+ override suspend fun getValues(keys: List<String>) = access.getValues(keys)
156
+ override suspend fun setValues(entries: List<Entry>) = access.setValues(entries)
157
+ override suspend fun removeValues(keys: List<String>) = access.removeValues(keys)
158
+ override suspend fun mergeValues(entries: List<Entry>) = access.mergeValues(entries)
159
+ override suspend fun getKeys() = access.getKeys()
160
+ override suspend fun clear() = access.clear()
161
+ }
@@ -0,0 +1,93 @@
1
+ package com.reactnativecommunity.asyncstorage.next
2
+
3
+ import com.facebook.react.bridge.JavaOnlyArray
4
+ import com.facebook.react.bridge.ReadableArray
5
+ import com.google.common.truth.Truth.assertThat
6
+ import org.junit.Assert.assertThrows
7
+ import org.junit.Test
8
+ import org.junit.runner.RunWith
9
+ import org.junit.runners.BlockJUnit4ClassRunner
10
+
11
+ @RunWith(BlockJUnit4ClassRunner::class)
12
+ class ArgumentHelpersTest {
13
+
14
+ @Test
15
+ fun transformsArgumentsToEntryList() {
16
+ val args = JavaOnlyArray.of(
17
+ arrayListOf("key1", "value1"),
18
+ arrayListOf("key2", "value2"),
19
+ arrayListOf("key3", "value3")
20
+ )
21
+ assertThat(args.toEntryList()).isEqualTo(
22
+ listOf(
23
+ Entry("key1", "value1"),
24
+ Entry("key2", "value2"),
25
+ Entry("key3", "value3"),
26
+ )
27
+ )
28
+ }
29
+
30
+ @Test
31
+ fun transfersArgumentsToKeyList() {
32
+ val keyList = listOf("key1", "key2", "key3")
33
+ val args = keyList.toReadableArray()
34
+ assertThat(args.toKeyList()).isEqualTo(keyList)
35
+ }
36
+
37
+ @Test
38
+ fun throwsIfArgumentsNotValidFormat() {
39
+ val invalid = arrayListOf("invalid")
40
+ val args = JavaOnlyArray.of(invalid)
41
+ val error = assertThrows(AsyncStorageError::class.java) {
42
+ args.toEntryList()
43
+ }
44
+
45
+ assertThat(error is AsyncStorageError).isTrue()
46
+ assertThat(error).hasMessageThat()
47
+ .isEqualTo("Invalid key-value format. Expected a list of [key, value] list.")
48
+ }
49
+
50
+ @Test
51
+ fun throwsIfArgumentKeyIsNullOrNotString() {
52
+ val argsInvalidNull = JavaOnlyArray.of(arrayListOf(null, "invalid"))
53
+ val errorArgsInvalidNull = assertThrows(AsyncStorageError::class.java) {
54
+ argsInvalidNull.toEntryList()
55
+ }
56
+ assertThat(errorArgsInvalidNull is AsyncStorageError).isTrue()
57
+ assertThat(errorArgsInvalidNull).hasMessageThat().isEqualTo("Key cannot be null.")
58
+
59
+ val notStringArgs = JavaOnlyArray.of(arrayListOf(123, "invalid"))
60
+ val errorNotString = assertThrows(AsyncStorageError::class.java) {
61
+ notStringArgs.toEntryList()
62
+ }
63
+ assertThat(errorNotString is AsyncStorageError).isTrue()
64
+ assertThat(errorNotString).hasMessageThat()
65
+ .isEqualTo("Provided key is not string. Only strings are supported as storage key.")
66
+ }
67
+
68
+ @Test
69
+ fun throwsIfArgumentValueNotString() {
70
+ val invalidArgs = JavaOnlyArray.of(arrayListOf("my_key", 666))
71
+ val error = assertThrows(AsyncStorageError::class.java) {
72
+ invalidArgs.toEntryList()
73
+ }
74
+ assertThat(error is AsyncStorageError).isTrue()
75
+ assertThat(error).hasMessageThat()
76
+ .isEqualTo("Value for key \"my_key\" is not a string. Only strings are supported as a value.")
77
+ }
78
+ }
79
+
80
+ fun List<Any?>.toReadableArray(): ReadableArray {
81
+ val arr = JavaOnlyArray()
82
+ forEach {
83
+ when (it) {
84
+ null -> arr.pushNull()
85
+ is Boolean -> arr.pushBoolean(it)
86
+ is Double -> arr.pushDouble(it)
87
+ is Int -> arr.pushInt(it)
88
+ is String -> arr.pushString(it)
89
+ else -> throw NotImplementedError()
90
+ }
91
+ }
92
+ return arr
93
+ }
@@ -0,0 +1,141 @@
1
+ package com.reactnativecommunity.asyncstorage.next
2
+
3
+ import androidx.room.Room
4
+ import androidx.test.ext.junit.runners.AndroidJUnit4
5
+ import androidx.test.platform.app.InstrumentationRegistry
6
+ import com.google.common.truth.Truth.assertThat
7
+ import kotlinx.coroutines.ExperimentalCoroutinesApi
8
+ import kotlinx.coroutines.runBlocking
9
+ import org.json.JSONObject
10
+ import org.junit.After
11
+ import org.junit.Before
12
+ import org.junit.Test
13
+ import org.junit.runner.RunWith
14
+ import kotlin.random.Random
15
+
16
+ @ExperimentalCoroutinesApi
17
+ @RunWith(AndroidJUnit4::class)
18
+ class AsyncStorageAccessTest {
19
+ private lateinit var asyncStorage: AsyncStorageAccess
20
+ private lateinit var database: StorageDb
21
+
22
+ @Before
23
+ fun setup() {
24
+ database = Room.inMemoryDatabaseBuilder(
25
+ InstrumentationRegistry.getInstrumentation().context, StorageDb::class.java
26
+ ).allowMainThreadQueries().build()
27
+ asyncStorage = StorageSupplier(database)
28
+ }
29
+
30
+ @After
31
+ fun tearDown() {
32
+ database.close()
33
+ }
34
+
35
+ @Test
36
+ fun performsBasicGetSetRemoveOperations() = runBlocking {
37
+ val entriesCount = 10
38
+ val entries = createRandomEntries(entriesCount)
39
+ val keys = entries.map { it.key }
40
+ assertThat(asyncStorage.getValues(keys)).hasSize(0)
41
+ asyncStorage.setValues(entries)
42
+ assertThat(asyncStorage.getValues(keys)).hasSize(entriesCount)
43
+ val indicesToRemove = (1..4).map { Random.nextInt(0, entriesCount) }.distinct()
44
+ val toRemove = entries.filterIndexed { index, _ -> indicesToRemove.contains(index) }
45
+ asyncStorage.removeValues(toRemove.map { it.key })
46
+ val currentEntries = asyncStorage.getValues(keys)
47
+ assertThat(currentEntries).hasSize(entriesCount - toRemove.size)
48
+ }
49
+
50
+ @Test
51
+ fun readsAllKeysAndClearsDb() = runBlocking {
52
+ val entries = createRandomEntries(8)
53
+ val keys = entries.map { it.key }
54
+ asyncStorage.setValues(entries)
55
+ val dbKeys = asyncStorage.getKeys()
56
+ assertThat(dbKeys).isEqualTo(keys)
57
+ asyncStorage.clear()
58
+ assertThat(asyncStorage.getValues(keys)).hasSize(0)
59
+ }
60
+
61
+ @Test
62
+ fun mergesDeeplyTwoValues() = runBlocking {
63
+ val initialEntry = Entry("key", VALUE_INITIAL)
64
+ val overrideEntry = Entry("key", VALUE_OVERRIDES)
65
+ asyncStorage.setValues(listOf(initialEntry))
66
+ asyncStorage.mergeValues(listOf(overrideEntry))
67
+ val current = asyncStorage.getValues(listOf("key"))[0]
68
+ assertThat(current.value).isEqualTo(VALUE_MERGED)
69
+ }
70
+
71
+ @Test
72
+ fun updatesExistingValues() = runBlocking {
73
+ val key = "test_key"
74
+ val value = "test_value"
75
+ val entries = listOf(Entry(key, value))
76
+ assertThat(asyncStorage.getValues(listOf(key))).hasSize(0)
77
+ asyncStorage.setValues(entries)
78
+ assertThat(asyncStorage.getValues(listOf(key))).isEqualTo(entries)
79
+ val modifiedEntries = listOf(Entry(key, "updatedValue"))
80
+ asyncStorage.setValues(modifiedEntries)
81
+ assertThat(asyncStorage.getValues(listOf(key))).isEqualTo(modifiedEntries)
82
+ }
83
+
84
+
85
+ // Test Helpers
86
+ private fun createRandomEntries(count: Int = Random.nextInt(10)): List<Entry> {
87
+ val entries = mutableListOf<Entry>()
88
+ for (i in 0 until count) {
89
+ entries.add(Entry("key$i", "value$i"))
90
+ }
91
+ return entries
92
+ }
93
+
94
+ private val VALUE_INITIAL = JSONObject(
95
+ """
96
+ {
97
+ "key":"value",
98
+ "key2":"override",
99
+ "key3":{
100
+ "key4":"value4",
101
+ "key6":{
102
+ "key7":"value7",
103
+ "key8":"override"
104
+ }
105
+ }
106
+ }
107
+ """.trimMargin()
108
+ ).toString()
109
+
110
+ private val VALUE_OVERRIDES = JSONObject(
111
+ """
112
+ {
113
+ "key2":"value2",
114
+ "key3":{
115
+ "key5":"value5",
116
+ "key6":{
117
+ "key8":"value8"
118
+ }
119
+ }
120
+ }
121
+ """
122
+ ).toString()
123
+
124
+
125
+ private val VALUE_MERGED = JSONObject(
126
+ """
127
+ {
128
+ "key":"value",
129
+ "key2":"value2",
130
+ "key3":{
131
+ "key4":"value4",
132
+ "key6":{
133
+ "key7":"value7",
134
+ "key8":"value8"
135
+ },
136
+ "key5":"value5"
137
+ }
138
+ }
139
+ """.trimMargin()
140
+ ).toString()
141
+ }
@@ -0,0 +1,38 @@
1
+
2
+ // pretty print test results
3
+ import org.gradle.api.tasks.testing.logging.TestExceptionFormat
4
+ import org.gradle.api.tasks.testing.logging.TestLogEvent
5
+ tasks.withType(Test) {
6
+ testLogging {
7
+
8
+ events TestLogEvent.FAILED,
9
+ TestLogEvent.PASSED,
10
+ TestLogEvent.SKIPPED,
11
+ TestLogEvent.STANDARD_OUT
12
+ exceptionFormat TestExceptionFormat.FULL
13
+ showExceptions true
14
+ showCauses true
15
+ showStackTraces true
16
+
17
+ debug {
18
+ events TestLogEvent.STARTED,
19
+ TestLogEvent.FAILED,
20
+ TestLogEvent.PASSED,
21
+ TestLogEvent.SKIPPED,
22
+ TestLogEvent.STANDARD_ERROR,
23
+ TestLogEvent.STANDARD_OUT
24
+ exceptionFormat TestExceptionFormat.FULL
25
+ }
26
+ info.events = debug.events
27
+ info.exceptionFormat = debug.exceptionFormat
28
+
29
+ afterSuite { desc, result ->
30
+ if (!desc.parent) { // will match the outermost suite
31
+ def output = "Results: ${result.resultType} (${result.testCount} tests, ${result.successfulTestCount} passed, ${result.failedTestCount} failed, ${result.skippedTestCount} skipped)"
32
+ def startItem = '| ', endItem = ' |'
33
+ def repeatLength = startItem.length() + output.length() + endItem.length()
34
+ println('\n' + ('-' * repeatLength) + '\n' + startItem + output + endItem + '\n' + ('-' * repeatLength))
35
+ }
36
+ }
37
+ }
38
+ }