Skip to content

Commit 75c571b

Browse files
author
Krzysztof
authored
feat: Use Room library for android persistent storage (#528)
1 parent a6957ba commit 75c571b

File tree

26 files changed

+1363
-72
lines changed

26 files changed

+1363
-72
lines changed

.circleci/config.yml

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ cache keys:
5252
brew ios: &key_brew_ios cache-brew-ios-v4-{{ arch }}
5353
brew android: &key_brew_android cache-brew-android-v4-{{ arch }}
5454
yarn: &key_yarn cache-yarn-{{ checksum "package.json" }}-{{ arch }}
55-
gradle: &key_gradle cache-gradle-{{ checksum "example/android/gradle/wrapper/gradle-wrapper.properties" }}-{{ checksum "package.json" }}-{{ arch }}
55+
gradle: &key_gradle cache-gradle-v1-{{ checksum "example/android/gradle/wrapper/gradle-wrapper.properties" }}-{{ checksum "package.json" }}-{{ arch }}
5656
pods: &key_pods cache-pods-v1-{{ checksum "example/ios/Podfile" }}-{{ checksum "package.json" }}-{{ arch }}
5757

5858
cache:
@@ -151,6 +151,26 @@ jobs:
151151
name: Flow check
152152
command: yarn test:flow
153153

154+
"Test: Android unit":
155+
<<: *android_defaults
156+
steps:
157+
- *addWorkspace
158+
- restore-cache: *cache_restore_yarn
159+
- run:
160+
name: Installing Yarn dependencies
161+
command: yarn --pure-lockfile --non-interactive --cache-folder ~/.cache/yarn
162+
- save-cache: *cache_save_yarn
163+
- restore-cache: *cache_restore_gradle
164+
- run:
165+
name: Downloading Gradle dependencies
166+
working_directory: example/android
167+
command: ./gradlew --max-workers 2 fetchDependencies
168+
- save-cache: *cache_save_gradle
169+
- run:
170+
name: Next storage tests
171+
working_directory: example/android
172+
command: ./gradlew react-native-async-storage_async-storage:test
173+
154174
"Test: iOS e2e":
155175
<<: *macos_defaults
156176
steps:
@@ -192,23 +212,16 @@ jobs:
192212
<<: *android_defaults
193213
steps:
194214
- *addWorkspace
195-
- restore-cache: *cache_restore_yarn
196-
- run:
197-
name: Installing Yarn dependencies
198-
command: yarn --pure-lockfile --non-interactive --cache-folder ~/.cache/yarn
199-
- save-cache: *cache_save_yarn
200-
- restore-cache: *cache_restore_gradle
201-
- run:
202-
name: Downloading Gradle dependencies
203-
working_directory: example/android
204-
command: ./gradlew --max-workers 2 fetchDependencies
205-
- save-cache: *cache_save_gradle
206215
- run:
207216
name: Bundle JS
208217
command: yarn bundle:android --dev false
209218
- run:
210-
name: Build APK
219+
name: Build APKs
211220
command: yarn build:e2e:android
221+
- run:
222+
name: Build APK with Next storage
223+
working_directory: example/android
224+
command: ./gradlew assembleNext --max-workers 2
212225
- persist_to_workspace:
213226
root: ~/async_storage
214227
paths:
@@ -272,7 +285,7 @@ jobs:
272285
-no-snapshot \
273286
-no-boot-anim \
274287
-no-window \
275-
-logcat '*:W' | grep -i "ReactNative"
288+
-logcat '*:E ReactNative:W ReactNativeJS:*'
276289
- run:
277290
name: Wait for emulator to boot
278291
command: ./scripts/android_e2e.sh 'wait_for_emulator'
@@ -283,6 +296,12 @@ jobs:
283296
- run:
284297
name: Run e2e tests
285298
command: yarn test:e2e:android
299+
- run:
300+
name: Clear previous app from device
301+
command: adb uninstall com.microsoft.reacttestapp
302+
- run:
303+
name: Run e2e tests for Next storage
304+
command: yarn detox test -c android.emu.release.next --maxConcurrency 1
286305

287306
Release:
288307
<<: *js_defaults
@@ -306,6 +325,9 @@ workflows:
306325
- "Test: flow":
307326
requires:
308327
- "Setup environment"
328+
- "Test: Android unit":
329+
requires:
330+
- "Setup environment"
309331
- "Test: iOS e2e":
310332
requires:
311333
- "Test: lint"
@@ -314,6 +336,7 @@ workflows:
314336
requires:
315337
- "Test: lint"
316338
- "Test: flow"
339+
- "Test: Android unit"
317340
- "Test: Android e2e":
318341
requires:
319342
- "Build: Android release apk"

android/build.gradle

Lines changed: 61 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,30 @@ def safeExtGet(prop, fallback) {
2121
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
2222
}
2323

24+
def getFlagOrDefault(flagName, defaultValue) {
25+
rootProject.hasProperty(flagName) ? rootProject.properties[flagName] == "true" : defaultValue
26+
}
27+
2428
configurations {
2529
compileClasspath
2630
}
2731

2832
buildscript {
29-
if (project == rootProject) {
30-
repositories {
31-
google()
32-
jcenter()
33-
}
34-
dependencies {
33+
// kotlin version is dictated by rootProject extension or property in gradle.properties
34+
ext.asyncStorageKtVersion = rootProject.ext.has('kotlinVersion')
35+
? rootProject.ext['kotlinVersion']
36+
: rootProject.hasProperty('AsyncStorage_kotlinVersion')
37+
? rootProject.properties['AsyncStorage_kotlinVersion']
38+
: '1.4.21'
39+
40+
repositories {
41+
google()
42+
jcenter()
43+
}
44+
dependencies {
45+
if (project == rootProject) {
3546
classpath 'com.android.tools.build:gradle:3.6.4'
47+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$asyncStorageKtVersion"
3648
}
3749
}
3850
}
@@ -42,18 +54,23 @@ buildscript {
4254
// This also protects the database from filling up the disk cache and becoming malformed.
4355
// If you really need bigger size, please keep in mind the potential consequences.
4456
long dbSizeInMB = 6L
45-
4657
def newDbSize = rootProject.properties['AsyncStorage_db_size_in_MB']
47-
48-
if( newDbSize != null && newDbSize.isLong()) {
58+
if (newDbSize != null && newDbSize.isLong()) {
4959
dbSizeInMB = newDbSize.toLong()
5060
}
5161

52-
def useDedicatedExecutor = rootProject.hasProperty('AsyncStorage_dedicatedExecutor')
53-
? rootProject.properties['AsyncStorage_dedicatedExecutor']
54-
: false
62+
// Instead of reusing AsyncTask thread pool, AsyncStorage can use its own executor
63+
def useDedicatedExecutor = getFlagOrDefault('AsyncStorage_dedicatedExecutor', false)
64+
65+
// Use next storage implementation
66+
def useNextStorage = getFlagOrDefault("AsyncStorage_useNextStorage", false)
5567

5668
apply plugin: 'com.android.library'
69+
if (useNextStorage) {
70+
apply plugin: 'kotlin-android'
71+
apply plugin: 'kotlin-kapt'
72+
apply from: './testresults.gradle'
73+
}
5774

5875
android {
5976
compileSdkVersion safeExtGet('compileSdkVersion', 28)
@@ -62,11 +79,18 @@ android {
6279
minSdkVersion safeExtGet('minSdkVersion', 19)
6380
targetSdkVersion safeExtGet('targetSdkVersion', 28)
6481
buildConfigField "Long", "AsyncStorage_db_size", "${dbSizeInMB}L"
65-
buildConfigField("boolean", "AsyncStorage_useDedicatedExecutor", "${useDedicatedExecutor}")
82+
buildConfigField "boolean", "AsyncStorage_useDedicatedExecutor", "${useDedicatedExecutor}"
83+
buildConfigField "boolean", "AsyncStorage_useNextStorage", "${useNextStorage}"
6684
}
6785
lintOptions {
6886
abortOnError false
6987
}
88+
89+
if (useNextStorage) {
90+
testOptions {
91+
unitTests.returnDefaultValues = true
92+
}
93+
}
7094
}
7195

7296
repositories {
@@ -79,6 +103,30 @@ repositories {
79103
}
80104

81105
dependencies {
106+
107+
if (useNextStorage) {
108+
def room_version = "2.2.6"
109+
def coroutines_version = "1.4.2"
110+
def junit_version = "4.12"
111+
def robolectric_version = "4.5.1"
112+
def truth_version = "1.1.2"
113+
def androidxtest_version = "1.1.0"
114+
def coroutinesTest_version = "1.4.2"
115+
116+
implementation "androidx.room:room-runtime:$room_version"
117+
implementation "androidx.room:room-ktx:$room_version"
118+
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
119+
kapt "androidx.room:room-compiler:$room_version"
120+
121+
testImplementation "junit:junit:$junit_version"
122+
testImplementation "androidx.test:runner:$androidxtest_version"
123+
testImplementation "androidx.test:rules:$androidxtest_version"
124+
testImplementation "androidx.test.ext:junit:$androidxtest_version"
125+
testImplementation "org.robolectric:robolectric:$robolectric_version"
126+
testImplementation "com.google.truth:truth:$truth_version"
127+
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesTest_version"
128+
}
129+
82130
//noinspection GradleDynamicVersion
83131
implementation 'com.facebook.react:react-native:+' // From node_modules
84132
}

android/gradle.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
android.useAndroidX=true

android/src/main/java/com/reactnativecommunity/asyncstorage/AsyncStorageModule.java

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -46,37 +46,6 @@ public final class AsyncStorageModule
4646
private ReactDatabaseSupplier mReactDatabaseSupplier;
4747
private boolean mShuttingDown = false;
4848

49-
// Adapted from https://android.googlesource.com/platform/frameworks/base.git/+/1488a3a19d4681a41fb45570c15e14d99db1cb66/core/java/android/os/AsyncTask.java#237
50-
private class SerialExecutor implements Executor {
51-
private final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
52-
private Runnable mActive;
53-
private final Executor executor;
54-
55-
SerialExecutor(Executor executor) {
56-
this.executor = executor;
57-
}
58-
59-
public synchronized void execute(final Runnable r) {
60-
mTasks.offer(new Runnable() {
61-
public void run() {
62-
try {
63-
r.run();
64-
} finally {
65-
scheduleNext();
66-
}
67-
}
68-
});
69-
if (mActive == null) {
70-
scheduleNext();
71-
}
72-
}
73-
synchronized void scheduleNext() {
74-
if ((mActive = mTasks.poll()) != null) {
75-
executor.execute(mActive);
76-
}
77-
}
78-
}
79-
8049
private final SerialExecutor executor;
8150

8251
public AsyncStorageModule(ReactApplicationContext reactContext) {

android/src/main/java/com/reactnativecommunity/asyncstorage/AsyncStoragePackage.java

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,30 +7,51 @@
77

88
package com.reactnativecommunity.asyncstorage;
99

10+
import android.util.Log;
1011
import com.facebook.react.ReactPackage;
1112
import com.facebook.react.bridge.JavaScriptModule;
1213
import com.facebook.react.bridge.NativeModule;
1314
import com.facebook.react.bridge.ReactApplicationContext;
15+
import com.facebook.react.bridge.ReactContext;
1416
import com.facebook.react.uimanager.ViewManager;
15-
16-
import java.util.Arrays;
17+
import java.util.ArrayList;
1718
import java.util.Collections;
1819
import java.util.List;
1920

2021
public class AsyncStoragePackage implements ReactPackage {
2122
@Override
2223
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
23-
return Arrays.<NativeModule>asList(new AsyncStorageModule(reactContext));
24+
25+
List<NativeModule> moduleList = new ArrayList<>(1);
26+
27+
if (BuildConfig.AsyncStorage_useNextStorage) {
28+
try {
29+
Class storageClass = Class.forName("com.reactnativecommunity.asyncstorage.next.StorageModule");
30+
NativeModule inst = (NativeModule) storageClass.getDeclaredConstructor(new Class[]{ReactContext.class}).newInstance(reactContext);
31+
moduleList.add(inst);
32+
} catch (Exception e) {
33+
String message = "Something went wrong when initializing module:"
34+
+ "\n"
35+
+ e.getCause().getClass()
36+
+ "\n"
37+
+ "Cause:" + e.getCause().getLocalizedMessage();
38+
Log.e("AsyncStorage_Next", message);
39+
}
40+
} else {
41+
moduleList.add(new AsyncStorageModule(reactContext));
42+
}
43+
44+
return moduleList;
2445
}
2546

2647
// Deprecated in RN 0.47
2748
public List<Class<? extends JavaScriptModule>> createJSModules() {
28-
return Collections.emptyList();
49+
return Collections.emptyList();
2950
}
3051

3152
@Override
3253
@SuppressWarnings("rawtypes")
3354
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
34-
return Collections.emptyList();
55+
return Collections.emptyList();
3556
}
3657
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package com.reactnativecommunity.asyncstorage;
2+
3+
import java.util.ArrayDeque;
4+
import java.util.concurrent.Executor;
5+
6+
/**
7+
* Detox is using this implementation detail in its environment setup,
8+
* so in order for Next storage to work, this class has been made public
9+
*
10+
* Adapted from https://android.googlesource.com/platform/frameworks/base.git/+/1488a3a19d4681a41fb45570c15e14d99db1cb66/core/java/android/os/AsyncTask.java#237
11+
*/
12+
public class SerialExecutor implements Executor {
13+
private final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
14+
private Runnable mActive;
15+
private final Executor executor;
16+
17+
public SerialExecutor(Executor executor) {
18+
this.executor = executor;
19+
}
20+
21+
public synchronized void execute(final Runnable r) {
22+
mTasks.offer(new Runnable() {
23+
public void run() {
24+
try {
25+
r.run();
26+
} finally {
27+
scheduleNext();
28+
}
29+
}
30+
});
31+
if (mActive == null) {
32+
scheduleNext();
33+
}
34+
}
35+
synchronized void scheduleNext() {
36+
if ((mActive = mTasks.poll()) != null) {
37+
executor.execute(mActive);
38+
}
39+
}
40+
}

0 commit comments

Comments
 (0)