diff --git a/.circleci/config.yml b/.circleci/config.yml index 3d552901..07c16eae 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -52,7 +52,7 @@ cache keys: brew ios: &key_brew_ios cache-brew-ios-v4-{{ arch }} brew android: &key_brew_android cache-brew-android-v4-{{ arch }} yarn: &key_yarn cache-yarn-{{ checksum "package.json" }}-{{ arch }} - gradle: &key_gradle cache-gradle-v1-{{ checksum "example/android/gradle/wrapper/gradle-wrapper.properties" }}-{{ checksum "package.json" }}-{{ arch }} + gradle: &key_gradle cache-gradle-v2-{{ checksum "example/android/gradle/wrapper/gradle-wrapper.properties" }}-{{ checksum "package.json" }}-{{ arch }} pods: &key_pods cache-pods-v1-{{ checksum "example/ios/Podfile" }}-{{ checksum "package.json" }}-{{ arch }} cache: diff --git a/android/build.gradle b/android/build.gradle index e11e6758..4e5ce774 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -25,6 +25,10 @@ def getFlagOrDefault(flagName, defaultValue) { rootProject.hasProperty(flagName) ? rootProject.properties[flagName] == "true" : defaultValue } +def getVersionOrDefault(String flagName, String defaultVersion) { + rootProject.hasProperty(flagName) ? rootProject.properties[flagName] : defaultVersion +} + configurations { compileClasspath } @@ -33,9 +37,7 @@ buildscript { // kotlin version is dictated by rootProject extension or property in gradle.properties ext.asyncStorageKtVersion = rootProject.ext.has('kotlinVersion') ? rootProject.ext['kotlinVersion'] - : rootProject.hasProperty('AsyncStorage_kotlinVersion') - ? rootProject.properties['AsyncStorage_kotlinVersion'] - : '1.4.21' + : getVersionOrDefault('AsyncStorage_kotlinVersion', '1.5.31') repositories { google() @@ -90,6 +92,13 @@ android { testOptions { unitTests.returnDefaultValues = true } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } } } @@ -106,16 +115,22 @@ repositories { dependencies { if (useNextStorage) { - def room_version = "2.2.6" - def coroutines_version = "1.4.2" + def room_version = getVersionOrDefault('AsyncStorage_next_roomVersion', '2.3.0') + def coroutines_version = "1.5.2" + def coroutinesTest_version = "1.5.2" + // if we don't provide explicit dependency on reflection, kotlin plugin + // would add one automatically, probably a version that is not compatible with + // used kotlin + def kotlinReflect_version = project.ext.asyncStorageKtVersion def junit_version = "4.12" def robolectric_version = "4.5.1" def truth_version = "1.1.2" def androidxtest_version = "1.1.0" - def coroutinesTest_version = "1.4.2" implementation "androidx.room:room-runtime:$room_version" implementation "androidx.room:room-ktx:$room_version" + implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlinReflect_version" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version" kapt "androidx.room:room-compiler:$room_version" diff --git a/example/android/build.gradle b/example/android/build.gradle index a03ce973..7afb732d 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -35,15 +35,17 @@ buildscript { rootProject.setProperty("AsyncStorage_useNextStorage", "true") } Boolean nextStorageFlag = rootProject.hasProperty("AsyncStorage_useNextStorage") ? rootProject.properties["AsyncStorage_useNextStorage"] == "true" : false - println("[Async Storage] Using Next storage: " + nextStorageFlag) + println("[AsyncStorage] Using Next storage: " + nextStorageFlag) repositories { google() mavenCentral() jcenter() } + dependencies { classpath "com.android.tools.build:gradle:$androidPluginVersion" + // kotlinVersion is applied from react-native-test-app's dependencies.gradle script classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" } } diff --git a/package.json b/package.json index 4c4f6adf..dcf0daf3 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,7 @@ "react-native": "0.63.4", "react-native-builder-bob": "^0.18.0", "react-native-macos": "^0.63.4", - "react-native-test-app": "^0.9.0", + "react-native-test-app": "^0.9.5", "react-native-web": "~0.12.0", "react-native-windows": "^0.63.41", "react-test-renderer": "16.13.1", diff --git a/src/AsyncStorage.native.js b/src/AsyncStorage.native.js index a195e386..123b22b8 100644 --- a/src/AsyncStorage.native.js +++ b/src/AsyncStorage.native.js @@ -282,10 +282,27 @@ const AsyncStorage = { return value; }); const reqLength = getRequests.length; + + /** + * As mentioned few lines above, this method could be called with the array of potential error, + * in case of anything goes wrong. The problem is, if any of the batched calls fails + * the rest of them would fail too, but the error would be consumed by just one. The rest + * would simply return `undefined` as their result, rendering false negatives. + * + * In order to avoid this situation, in case of any call failing, + * the rest of them will be rejected as well (with the same error). + */ + const errorList = convertErrors(errors); + const error = errorList && errorList.length ? errorList[0] : null; + for (let i = 0; i < reqLength; i++) { const request = getRequests[i]; - const requestKeys = request.keys; - const requestResult = requestKeys.map((key) => [key, map[key]]); + if (error) { + request.callback && request.callback(error); + request.reject && request.reject(error); + continue; + } + const requestResult = request.keys.map((key) => [key, map[key]]); request.callback && request.callback(null, requestResult); request.resolve && request.resolve(requestResult); } diff --git a/website/docs/Limits.md b/website/docs/Limits.md new file mode 100644 index 00000000..9ece909c --- /dev/null +++ b/website/docs/Limits.md @@ -0,0 +1,17 @@ +--- +id: limits +title: Known storage limits +sidebar_label: Known limits +--- + +## Android + +AsyncStorage for Android uses SQLite for storage backend. While it has [its own size limits](https://www.sqlite.org/limits.html), Android system also have two known limits: total storage size and per-entry size limit. + + +- Total storage size is capped at 6 MB by default. You can increase this size by [specifying a new size using feature flag.](advanced/IncreaseDbSize.md) + +- Per-entry is limited by a size of a WindowCursor, a buffer used to read data from SQLite. [Currently it's size is around 2 MB](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/res/res/values/config.xml;l=2103). + This means that the single item read at one time cannot be larger than 2 MB. There's no supported workaround from AsyncStorage. + We suggest keeping your data lower than that, by chopping it down into many entries, instead of one massive entry. + This is where [`multiGet`](API.md#multiget) and [`multiSet`](API.md#multiset) APIs can shine. diff --git a/website/docs/advanced/Next.md b/website/docs/advanced/Next.md index c01e3009..291637b4 100644 --- a/website/docs/advanced/Next.md +++ b/website/docs/advanced/Next.md @@ -16,12 +16,12 @@ Current implementation of persistence layer is created using [SQLiteOpenHelper]( a helper class that manages database creation and migrations. Even if this approach is powerful, the lack of compile time query verification and a big boilerplate of mapping SQLite queries to actual values make this implementation prone to many errors. This Async Storage feature improves the persistence layer, using modern approaches to access SQLite (using [Room](https://developer.android.com/training/data-storage/room)), to reduce possible anomalies to the minimum. -On top of that, it allows to access Async Storage from the native side, useful in [Brownfield integration.](BrownfieldIntegration.md#android) +On top of that, it allows accessing AsyncStorage from the native side, useful in [Brownfield integration.](BrownfieldIntegration.md#android) ### Migration This feature requires no migration from the developer perspective - the current database will be recreated (based on the current one), meaning user won't lose any data if you decide to opt in. -There's a small drawback to know - the database "recreation" happens **only once**. Unless you want to disable the feature in the future, there's nothing to worry about. +There's a small drawback to know - the database "recreation" happens **only once**. #### How it works @@ -36,7 +36,9 @@ If you decide to disable the feature, your users will be back using old database When you enable the feature again, the new database is **not** recreated, because it already exists, and no data is copied over. -### Enabling +### Enable + +See [Configuration](#configuration) section below to learn more about setting different versions of Kotlin or Room. 1. In your project's `android` directory, locate root `build.gradle` file. Add Kotlin dependency to the `buildscript`: @@ -44,7 +46,7 @@ When you enable the feature again, the new database is **not** recreated, becaus buildscript { ext { // other extensions -+ kotlinVersion = '1.4.21' ++ kotlinVersion = '1.5.31' } dependencies { @@ -55,26 +57,38 @@ buildscript { ``` -2. In the same directory (normally `android`) locate `gradle.properties` file (if does not exists, create one) and add the line: +2. In the same directory (normally `android`) locate `gradle.properties` file (if it does not exist, create one) and add the line: ```groovy AsyncStorage_useNextStorage=true ``` -**How to specifying Kotlin version** +### Configuration + +**Kotlin version** -Supported Kotlin versions are `1.4.x`. You can specify which one to use in two ways: +Next storage is tested against Kotlin version `1.5.31`. +You can specify different version, in one of two ways: -- having an `kotlinVersion` extension on the `rootProject` (recommended): +- add `kotlinVersion` extension to the `rootProject`: ```groovy -rootProject.ext.kotlinVersion = '1.4.21' +rootProject.ext.kotlinVersion = '1.5.31' ``` - specify `AsyncStorage_kotlinVersion` in `gradle.properties`: ```groovy -AsyncStorage_kotlinVersion=1.4.21 +AsyncStorage_kotlinVersion=1.5.31 +``` + +**Room** + +Next AsyncStorage uses [Room persistence library](https://developer.android.com/jetpack/androidx/releases/room) to store data. +Currently, tested version is `2.3.0`. You can specify different version, by adding a flag to `gradle.properties`: + +```groovy +AsyncStorage_next_roomVersion=2.3.0 ``` ### Notable changes diff --git a/website/sidebars.js b/website/sidebars.js index 96a270d4..f05b3669 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -1,6 +1,6 @@ module.exports = { docs: { - 'Getting started': ['install', 'usage', 'link', 'api'], + 'Getting started': ['install', 'usage', 'link', 'api', 'limits'], Advanced: ['advanced/next', 'advanced/jest', 'advanced/brownfield', 'advanced/backup', 'advanced/executor', 'advanced/db_size'], Debugging: ['debugging/communityPackages'], Help: ['help/troubleshooting'], diff --git a/yarn.lock b/yarn.lock index c0d50b48..bee17d46 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11835,15 +11835,16 @@ react-native-safe-area-context@~3.0.7: resolved "https://registry.yarnpkg.com/react-native-safe-area-context/-/react-native-safe-area-context-3.0.7.tgz#0f53de7a30d626d82936000f3f6db374ecc4b800" integrity sha512-dqhRTlIFe5+P1yxitj0C9XVUxLqOmjomeqzUSSY8sNOWVjtIhEY/fl4ZKYpAVnktd8dt3zl13XmJTmRmy3d0uA== -react-native-test-app@^0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/react-native-test-app/-/react-native-test-app-0.9.0.tgz#fd8669fd34703a02bfdab9ba7a98268002cc2fa4" - integrity sha512-HMI15lJqWZEOA1O5VvzxYG7mEzuKhkztxarWqVibUhcgOsK3U8fidq2+b18E1BiFpaDI+4zppkib/+/5LTjReQ== +react-native-test-app@^0.9.5: + version "0.9.11" + resolved "https://registry.yarnpkg.com/react-native-test-app/-/react-native-test-app-0.9.11.tgz#685c1252a4133531ba9590dbed0fa92c9fa5da35" + integrity sha512-XpysqX3UBb182ctcm02keNc0gjrS+Ae8kYfhZzR53zm3JqMY3gAluAmHwEKFzeHrGaL0mWtvvMTutex+VF1Ttw== dependencies: chalk "^4.1.0" prompts "^2.4.0" rimraf "^3.0.0" semver "^7.3.5" + uuid "^8.3.2" yargs "^16.0.0" react-native-web@~0.12.0: @@ -14122,7 +14123,7 @@ uuid@^7.0.3: resolved "https://registry.yarnpkg.com/uuid/-/uuid-7.0.3.tgz#c5c9f2c8cf25dc0a372c4df1441c41f5bd0c680b" integrity sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg== -uuid@^8.3.0: +uuid@^8.3.0, uuid@^8.3.2: version "8.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==