Skip to content

Commit a545d0a

Browse files
author
Krzysztof Borowy
committed
merging values
1 parent 0bc83db commit a545d0a

File tree

6 files changed

+181
-19
lines changed

6 files changed

+181
-19
lines changed

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ public List<NativeModule> createNativeModules(ReactApplicationContext reactConte
3030
NativeModule inst = (NativeModule) storageClass.getDeclaredConstructor(new Class[]{ReactContext.class}).newInstance(reactContext);
3131
moduleList.add(inst);
3232
} catch (Exception e) {
33-
// todo notify about potential issues
3433
String message = "Something went wrong when initializing module:"
3534
+ "\n"
3635
+ e.getCause().getClass()

android/src/main/java/com/reactnativecommunity/asyncstorage/next/ArgumentHelpers.kt

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package com.reactnativecommunity.asyncstorage.next
22

33
import com.facebook.react.bridge.Arguments
44
import com.facebook.react.bridge.ReadableArray
5+
import org.json.JSONException
6+
import org.json.JSONObject
57

68
fun ReadableArray.toEntryList(): List<Entry> {
79
val list = mutableListOf<Entry>()
@@ -50,4 +52,35 @@ fun List<Entry>.toKeyValueArgument(): ReadableArray {
5052
}
5153

5254
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
5386
}

android/src/main/java/com/reactnativecommunity/asyncstorage/next/StorageModule.kt

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ class StorageModule(reactContext: ReactContext) : ReactContextBaseJavaModule(),
2222

2323
/**
2424
* Todo:
25-
* - MultiMerge
26-
* - Documenting the migration, access from Brownfield and error handling
25+
* - Documenting the migration,
26+
* - access from Native (Java interop)
2727
*/
2828

2929
@ReactMethod
@@ -51,8 +51,14 @@ class StorageModule(reactContext: ReactContext) : ReactContextBaseJavaModule(),
5151
}
5252
}
5353

54-
// @ReactMethod
55-
// fun multiMerge(val keyValueArray: ReadableArray, cb: Callback) {}
54+
@ReactMethod
55+
fun multiMerge(keyValueArray: ReadableArray, cb: Callback) {
56+
launch(createExceptionHandler(cb)) {
57+
val entries = keyValueArray.toEntryList()
58+
storage.mergeValues(entries)
59+
cb(null)
60+
}
61+
}
5662

5763
@ReactMethod
5864
fun getAllKeys(cb: Callback) {

android/src/main/java/com/reactnativecommunity/asyncstorage/next/StorageSupplier.kt

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import androidx.room.Transaction
1515
import androidx.room.Update
1616
import androidx.room.migration.Migration
1717
import androidx.sqlite.db.SupportSQLiteDatabase
18+
import org.json.JSONObject
1819

1920
private const val DATABASE_VERSION = 2
2021
private const val DATABASE_NAME = "AsyncStorage"
@@ -32,7 +33,25 @@ data class Entry(
3233
@Dao
3334
private interface StorageDao {
3435

35-
// fun mergeValues() // todo
36+
@Transaction
37+
fun mergeValues(entries: List<Entry>) {
38+
val currentDbEntries = getValues(entries.map { it.key })
39+
val newEntries = mutableListOf<Entry>()
40+
41+
entries.forEach { newEntry ->
42+
val oldEntry = currentDbEntries.find { it.key == newEntry.key }
43+
if (oldEntry?.value == null) {
44+
newEntries.add(newEntry)
45+
} else if (!oldEntry.value.isValidJson() || !newEntry.value.isValidJson()) {
46+
newEntries.add(newEntry)
47+
} else {
48+
val newValue =
49+
JSONObject(oldEntry.value).mergeWith(JSONObject(newEntry.value)).toString()
50+
newEntries.add(newEntry.copy(value = newValue))
51+
}
52+
}
53+
setValues(newEntries)
54+
}
3655

3756
@Transaction
3857
@Query("SELECT * FROM $TABLE_NAME WHERE `$COLUMN_KEY` IN (:keys)")
@@ -138,7 +157,7 @@ interface AsyncStorageAccess {
138157
suspend fun removeValues(keys: List<String>)
139158
suspend fun getKeys(): List<String>
140159
suspend fun clear()
141-
// suspend fun mergeValues() // todo
160+
suspend fun mergeValues(entries: List<Entry>)
142161
}
143162

144163
class StorageSupplier private constructor(db: StorageDb) : AsyncStorageAccess {
@@ -153,6 +172,7 @@ class StorageSupplier private constructor(db: StorageDb) : AsyncStorageAccess {
153172
override suspend fun getValues(keys: List<String>) = access.getValues(keys)
154173
override suspend fun setValues(entries: List<Entry>) = access.setValues(entries)
155174
override suspend fun removeValues(keys: List<String>) = access.removeValues(keys)
175+
override suspend fun mergeValues(entries: List<Entry>) = access.mergeValues(entries)
156176
override suspend fun getKeys() = access.getKeys()
157177
override suspend fun clear() = access.clear()
158178
}

example/App.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ const styles = StyleSheet.create({
128128
padding: 8,
129129
},
130130
exampleContainer: {
131-
padding: 16,
131+
padding: 4,
132132
backgroundColor: '#FFF',
133133
borderColor: '#EEE',
134134
borderTopWidth: 1,

example/examples/Basic.js

Lines changed: 115 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,33 @@
11
import React from 'react';
2-
import {View, Text, Button, StyleSheet, ScrollView, TextInput, NativeModules} from 'react-native';
2+
import {View, Text, Button, StyleSheet, ScrollView, TextInput} from 'react-native';
33
import AsyncStorage from '@react-native-async-storage/async-storage';
44

5-
const RCTAsyncStorage = NativeModules.RNC_AsyncSQLiteDBStorage ||
6-
NativeModules.RNCAsyncStorage;
75

6+
const mergeInitialValue = {
7+
initial: 'keep',
8+
override1: 'override',
9+
nested: {
10+
nestedValue: 'keep',
11+
override2: 'override',
12+
deeper: {
13+
deeperValue: 'keep',
14+
override3: 'override',
15+
},
16+
},
17+
};
818

919
function NextExample() {
1020
const [keys, setKeys] = React.useState([]);
1121
const [error, setError] = React.useState(null);
1222
const [inputKey, setInputKey] = React.useState();
1323
const [inputValue, setInputValue] = React.useState();
1424
const [value, setValue] = React.useState();
25+
const [mergedValue, setMergedValue] = React.useState();
26+
const [overrideValue, setOverrideValue] = React.useState({
27+
override1: '',
28+
override2: '',
29+
override3: '',
30+
});
1531

1632

1733
function runWithCatch(block) {
@@ -59,14 +75,58 @@ function NextExample() {
5975
await AsyncStorage.clear();
6076
}
6177

78+
async function resetMergedValue() {
79+
await AsyncStorage.setItem('MERGER', JSON.stringify(mergeInitialValue));
80+
const saved = await AsyncStorage.getItem('MERGER');
81+
setMergedValue(JSON.parse(saved));
82+
}
83+
84+
async function readMergedValue() {
85+
const saved = await AsyncStorage.getItem('MERGER');
86+
setMergedValue(saved ? JSON.parse(saved) : {});
87+
}
88+
89+
async function mergeValues() {
90+
const {override1, override2, override3} = overrideValue;
91+
92+
// leave out empty inputs
93+
const toMerge = {};
94+
if (override1) {
95+
toMerge.override1 = override1;
96+
}
97+
if (override2) {
98+
toMerge.nested = {
99+
override2: override2,
100+
};
101+
}
102+
if (override3) {
103+
if (!toMerge.nested) {
104+
toMerge.nested = {
105+
deeper: {
106+
override3: override3,
107+
},
108+
};
109+
} else {
110+
toMerge.nested.deeper = {
111+
override3: override3,
112+
};
113+
}
114+
}
115+
116+
117+
await AsyncStorage.mergeItem('MERGER', JSON.stringify(toMerge));
118+
}
119+
120+
62121
return <ScrollView contentContainerStyle={{flexGrow: 1}}>
63122

64-
{error ? <Text style={{fontSize: 18, color: 'red'}}>{error}</Text> : null}
123+
{error ? <Text style={styles.error}>{error}</Text> : null}
65124

66125
<View style={styles.example}>
126+
<Text style={styles.title}>Basic operations</Text>
67127
<TextInput onChangeText={setInputKey} value={inputKey} style={styles.input} placeholder="key"/>
68128
<TextInput onChangeText={setInputValue} value={inputValue} style={styles.input} placeholder="value"/>
69-
<View style={{flexDirection: 'row', justifyContent: 'space-around'}}>
129+
<View style={styles.row}>
70130
<Button title="Read" onPress={runWithCatch(readValue)}/>
71131
<Button title="Save" onPress={runWithCatch(saveValue)}/>
72132
<Button title="Delete" onPress={runWithCatch(removeValue)}/>
@@ -75,24 +135,54 @@ function NextExample() {
75135
</View>
76136

77137
<View style={styles.example}>
78-
<Text style={{fontSize: 16, fontWeight: '700'}}>Crash scenarios</Text>
79-
<View style={{flexDirection: 'row', justifyContent: 'space-around'}}>
138+
<Text style={styles.title}>Crash scenarios</Text>
139+
<View style={styles.row}>
80140
<Button title="Key null" onPress={runWithCatch(crashKeyNull)}/>
81141
<Button title="Key not string" onPress={runWithCatch(crashKeyNotString)}/>
82142
<Button title="Wrong value type" onPress={runWithCatch(crashValueType)}/>
83143
</View>
84144
</View>
85145

86146
<View style={styles.example}>
87-
<View style={{flexDirection: 'row', justifyContent: 'space-around'}}>
147+
<Text style={styles.title}>Merging</Text>
148+
<View style={styles.row}>
149+
<Text>{`Value:\n\n${JSON.stringify(mergedValue, null, 2)}`}</Text>
150+
<Text>{`Merge with:\n\n${JSON.stringify(overrideValue, null, 2)}`}</Text>
151+
</View>
152+
<View style={styles.row}>
153+
<Button title="Read" onPress={runWithCatch(readMergedValue)}/>
154+
<Button title="Reset" onPress={runWithCatch(resetMergedValue)}/>
155+
<Button title="Merge" onPress={runWithCatch(mergeValues)}/>
156+
</View>
157+
<View>
158+
<TextInput
159+
onChangeText={t => setOverrideValue(c => ({...c, override1: t}))}
160+
value={overrideValue.override1}
161+
style={styles.input} placeholder="override1"
162+
/>
163+
<TextInput
164+
onChangeText={t => setOverrideValue(c => ({...c, override2: t}))}
165+
value={overrideValue.override2}
166+
style={styles.input} placeholder="override2"
167+
/>
168+
<TextInput
169+
onChangeText={t => setOverrideValue(c => ({...c, override3: t}))}
170+
value={overrideValue.override3}
171+
style={styles.input} placeholder="override3"
172+
/>
173+
</View>
174+
</View>
175+
176+
<View style={styles.example}>
177+
<Text style={styles.title}>Display all keys</Text>
178+
<View style={styles.row}>
88179
<Button title="Get all keys" onPress={runWithCatch(getAllKeys)}/>
89180
</View>
90-
<Text style={{fontWeight: '700'}}>Keys:</Text>
91181
<Text>{keys.join(', ')}</Text>
92182
</View>
93183

94184
<View style={styles.example}>
95-
<Text style={{fontSize: 16}}>Clear database entries</Text>
185+
<Text style={styles.title}>Clear database entries</Text>
96186
<Button title="clear" onPress={runWithCatch(clearDb)}/>
97187
</View>
98188
</ScrollView>;
@@ -101,7 +191,8 @@ function NextExample() {
101191

102192
const styles = StyleSheet.create({
103193
example: {
104-
paddingVertical: 24,
194+
paddingBottom: 24,
195+
paddingTop: 8,
105196
borderBottomWidth: 1,
106197
borderBottomColor: '#e3e3e3',
107198
borderStyle: 'solid',
@@ -112,6 +203,19 @@ const styles = StyleSheet.create({
112203
borderColor: '#eee',
113204
borderStyle: 'solid',
114205
},
206+
error: {
207+
fontSize: 18,
208+
color: 'red',
209+
},
210+
row: {
211+
flexDirection: 'row',
212+
justifyContent: 'space-around',
213+
},
214+
title: {
215+
fontSize: 16,
216+
fontWeight: '700',
217+
paddingBottom: 12,
218+
},
115219
});
116220

117221

0 commit comments

Comments
 (0)