2015-03-11 19:06:03 +00:00
|
|
|
/**
|
2015-03-23 20:35:08 +00:00
|
|
|
* Copyright (c) 2015-present, Facebook, Inc.
|
|
|
|
* All rights reserved.
|
|
|
|
*
|
|
|
|
* This source code is licensed under the BSD-style license found in the
|
|
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
2015-03-11 19:06:03 +00:00
|
|
|
*
|
|
|
|
* @providesModule AsyncStorage
|
2015-12-02 03:09:01 +00:00
|
|
|
* @noflow
|
2015-03-11 19:06:03 +00:00
|
|
|
* @flow-weak
|
|
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
|
2015-03-17 10:08:49 +00:00
|
|
|
var NativeModules = require('NativeModules');
|
2015-10-27 16:59:30 +00:00
|
|
|
var RCTAsyncSQLiteStorage = NativeModules.AsyncSQLiteDBStorage;
|
2015-03-18 05:22:03 +00:00
|
|
|
var RCTAsyncRocksDBStorage = NativeModules.AsyncRocksDBStorage;
|
2015-10-27 16:59:30 +00:00
|
|
|
var RCTAsyncFileStorage = NativeModules.AsyncLocalStorage;
|
2015-03-11 19:06:03 +00:00
|
|
|
|
2015-10-27 16:59:30 +00:00
|
|
|
// Use RocksDB if available, then SQLite, then file storage.
|
|
|
|
var RCTAsyncStorage = RCTAsyncRocksDBStorage || RCTAsyncSQLiteStorage || RCTAsyncFileStorage;
|
2015-03-11 19:06:03 +00:00
|
|
|
|
|
|
|
/**
|
2015-06-15 21:17:27 +00:00
|
|
|
* AsyncStorage is a simple, asynchronous, persistent, key-value storage
|
|
|
|
* system that is global to the app. It should be used instead of LocalStorage.
|
2015-03-11 19:06:03 +00:00
|
|
|
*
|
|
|
|
* It is recommended that you use an abstraction on top of AsyncStorage instead
|
|
|
|
* of AsyncStorage directly for anything more than light usage since it
|
|
|
|
* operates globally.
|
|
|
|
*
|
2015-06-15 21:17:27 +00:00
|
|
|
* This JS code is a simple facade over the native iOS implementation to provide
|
2015-04-07 09:02:05 +00:00
|
|
|
* a clear JS API, real Error objects, and simple non-multi functions. Each
|
|
|
|
* method returns a `Promise` object.
|
2015-03-11 19:06:03 +00:00
|
|
|
*/
|
|
|
|
var AsyncStorage = {
|
2015-12-03 16:08:37 +00:00
|
|
|
_getRequests: ([]: Array<any>),
|
|
|
|
_getKeys: ([]: Array<string>),
|
|
|
|
_immediate: (null: ?number),
|
|
|
|
|
2015-03-11 19:06:03 +00:00
|
|
|
/**
|
|
|
|
* Fetches `key` and passes the result to `callback`, along with an `Error` if
|
2015-04-07 09:02:05 +00:00
|
|
|
* there is any. Returns a `Promise` object.
|
2015-03-11 19:06:03 +00:00
|
|
|
*/
|
|
|
|
getItem: function(
|
|
|
|
key: string,
|
2015-05-02 01:29:43 +00:00
|
|
|
callback?: ?(error: ?Error, result: ?string) => void
|
2015-04-07 09:02:05 +00:00
|
|
|
): Promise {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
RCTAsyncStorage.multiGet([key], function(errors, result) {
|
|
|
|
// Unpack result to get value from [[key,value]]
|
|
|
|
var value = (result && result[0] && result[0][1]) ? result[0][1] : null;
|
2015-10-28 18:59:06 +00:00
|
|
|
var errs = convertErrors(errors);
|
|
|
|
callback && callback(errs && errs[0], value);
|
|
|
|
if (errs) {
|
|
|
|
reject(errs[0]);
|
2015-04-07 09:02:05 +00:00
|
|
|
} else {
|
|
|
|
resolve(value);
|
|
|
|
}
|
|
|
|
});
|
2015-03-11 19:06:03 +00:00
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets `value` for `key` and calls `callback` on completion, along with an
|
2015-04-07 09:02:05 +00:00
|
|
|
* `Error` if there is any. Returns a `Promise` object.
|
2015-03-11 19:06:03 +00:00
|
|
|
*/
|
|
|
|
setItem: function(
|
|
|
|
key: string,
|
|
|
|
value: string,
|
2015-05-02 01:29:43 +00:00
|
|
|
callback?: ?(error: ?Error) => void
|
2015-04-07 09:02:05 +00:00
|
|
|
): Promise {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
RCTAsyncStorage.multiSet([[key,value]], function(errors) {
|
2015-10-28 18:59:06 +00:00
|
|
|
var errs = convertErrors(errors);
|
|
|
|
callback && callback(errs && errs[0]);
|
|
|
|
if (errs) {
|
|
|
|
reject(errs[0]);
|
2015-04-07 09:02:05 +00:00
|
|
|
} else {
|
|
|
|
resolve(null);
|
|
|
|
}
|
|
|
|
});
|
2015-03-11 19:06:03 +00:00
|
|
|
});
|
|
|
|
},
|
2015-10-27 16:59:30 +00:00
|
|
|
|
2015-04-07 09:02:05 +00:00
|
|
|
/**
|
|
|
|
* Returns a `Promise` object.
|
|
|
|
*/
|
2015-03-11 19:06:03 +00:00
|
|
|
removeItem: function(
|
|
|
|
key: string,
|
2015-05-02 01:29:43 +00:00
|
|
|
callback?: ?(error: ?Error) => void
|
2015-04-07 09:02:05 +00:00
|
|
|
): Promise {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
RCTAsyncStorage.multiRemove([key], function(errors) {
|
2015-10-28 18:59:06 +00:00
|
|
|
var errs = convertErrors(errors);
|
|
|
|
callback && callback(errs && errs[0]);
|
|
|
|
if (errs) {
|
|
|
|
reject(errs[0]);
|
2015-04-07 09:02:05 +00:00
|
|
|
} else {
|
|
|
|
resolve(null);
|
|
|
|
}
|
|
|
|
});
|
2015-03-11 19:06:03 +00:00
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2015-10-27 16:59:30 +00:00
|
|
|
* Merges existing value with input value, assuming they are stringified json.
|
|
|
|
* Returns a `Promise` object. Not supported by all native implementations.
|
2016-03-15 00:37:34 +00:00
|
|
|
*
|
|
|
|
* Example:
|
|
|
|
* ```javascript
|
|
|
|
* let UID123_object = {
|
|
|
|
* name: 'Chris',
|
|
|
|
* age: 30,
|
|
|
|
* traits: {hair: 'brown', eyes: 'brown'},
|
|
|
|
* };
|
|
|
|
|
|
|
|
// need only define what will be added or updated
|
|
|
|
* let UID123_delta = {
|
|
|
|
* age: 31,
|
|
|
|
* traits: {eyes: 'blue', shoe_size: 10}
|
|
|
|
* };
|
|
|
|
|
|
|
|
* AsyncStorage.setItem(store_key, JSON.stringify(UID123_object), () => {
|
|
|
|
* AsyncStorage.mergeItem('UID123', JSON.stringify(UID123_delta), () => {
|
|
|
|
* AsyncStorage.getItem('UID123', (err, result) => {
|
|
|
|
* console.log(result);
|
|
|
|
* // => {'name':'Chris','age':31,'traits':{'shoe_size':10,'hair':'brown','eyes':'blue'}}
|
|
|
|
* });
|
|
|
|
* });
|
|
|
|
* });
|
|
|
|
* ```
|
2015-03-11 19:06:03 +00:00
|
|
|
*/
|
|
|
|
mergeItem: function(
|
|
|
|
key: string,
|
|
|
|
value: string,
|
2015-05-02 01:29:43 +00:00
|
|
|
callback?: ?(error: ?Error) => void
|
2015-04-07 09:02:05 +00:00
|
|
|
): Promise {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
RCTAsyncStorage.multiMerge([[key,value]], function(errors) {
|
2015-10-28 18:59:06 +00:00
|
|
|
var errs = convertErrors(errors);
|
|
|
|
callback && callback(errs && errs[0]);
|
|
|
|
if (errs) {
|
|
|
|
reject(errs[0]);
|
2015-04-07 09:02:05 +00:00
|
|
|
} else {
|
|
|
|
resolve(null);
|
|
|
|
}
|
|
|
|
});
|
2015-03-11 19:06:03 +00:00
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Erases *all* AsyncStorage for all clients, libraries, etc. You probably
|
|
|
|
* don't want to call this - use removeItem or multiRemove to clear only your
|
2015-04-07 09:02:05 +00:00
|
|
|
* own keys instead. Returns a `Promise` object.
|
2015-03-11 19:06:03 +00:00
|
|
|
*/
|
2015-05-02 01:29:43 +00:00
|
|
|
clear: function(callback?: ?(error: ?Error) => void): Promise {
|
2015-04-07 09:02:05 +00:00
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
RCTAsyncStorage.clear(function(error) {
|
|
|
|
callback && callback(convertError(error));
|
|
|
|
if (error && convertError(error)){
|
|
|
|
reject(convertError(error));
|
|
|
|
} else {
|
|
|
|
resolve(null);
|
|
|
|
}
|
|
|
|
});
|
2015-03-11 19:06:03 +00:00
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2015-06-15 21:17:27 +00:00
|
|
|
* Gets *all* keys known to the app, for all callers, libraries, etc. Returns a `Promise` object.
|
2016-03-15 00:37:34 +00:00
|
|
|
*
|
|
|
|
* Example: see multiGet for example
|
2015-03-11 19:06:03 +00:00
|
|
|
*/
|
2015-05-02 01:29:43 +00:00
|
|
|
getAllKeys: function(callback?: ?(error: ?Error, keys: ?Array<string>) => void): Promise {
|
2015-04-07 09:02:05 +00:00
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
RCTAsyncStorage.getAllKeys(function(error, keys) {
|
|
|
|
callback && callback(convertError(error), keys);
|
|
|
|
if (error) {
|
|
|
|
reject(convertError(error));
|
|
|
|
} else {
|
|
|
|
resolve(keys);
|
|
|
|
}
|
|
|
|
});
|
2015-03-11 19:06:03 +00:00
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The following batched functions are useful for executing a lot of
|
|
|
|
* operations at once, allowing for native optimizations and provide the
|
|
|
|
* convenience of a single callback after all operations are complete.
|
|
|
|
*
|
|
|
|
* These functions return arrays of errors, potentially one for every key.
|
|
|
|
* For key-specific errors, the Error object will have a key property to
|
|
|
|
* indicate which key caused the error.
|
|
|
|
*/
|
|
|
|
|
2015-12-03 16:08:37 +00:00
|
|
|
/** Flushes any pending requests using a single multiget */
|
|
|
|
flushGetRequests: function(): void {
|
|
|
|
const getRequests = this._getRequests;
|
|
|
|
const getKeys = this._getKeys;
|
|
|
|
|
|
|
|
this._getRequests = [];
|
|
|
|
this._getKeys = [];
|
|
|
|
|
|
|
|
RCTAsyncStorage.multiGet(getKeys, function(errors, result) {
|
|
|
|
// Even though the runtime complexity of this is theoretically worse vs if we used a map,
|
|
|
|
// it's much, much faster in practice for the data sets we deal with (we avoid
|
|
|
|
// allocating result pair arrays). This was heavily benchmarked.
|
2016-02-06 00:44:44 +00:00
|
|
|
//
|
|
|
|
// Is there a way to avoid using the map but fix the bug in this breaking test?
|
|
|
|
// https://github.com/facebook/react-native/commit/8dd8ad76579d7feef34c014d387bf02065692264
|
|
|
|
let map = {};
|
|
|
|
result.forEach(([key, value]) => map[key] = value);
|
2015-12-03 16:08:37 +00:00
|
|
|
const reqLength = getRequests.length;
|
|
|
|
for (let i = 0; i < reqLength; i++) {
|
|
|
|
const request = getRequests[i];
|
|
|
|
const requestKeys = request.keys;
|
2016-02-06 00:44:44 +00:00
|
|
|
let requestResult = requestKeys.map(key => [key, map[key]]);
|
2015-12-03 16:08:37 +00:00
|
|
|
request.callback && request.callback(null, requestResult);
|
|
|
|
request.resolve && request.resolve(requestResult);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2015-03-11 19:06:03 +00:00
|
|
|
/**
|
|
|
|
* multiGet invokes callback with an array of key-value pair arrays that
|
2015-04-07 09:02:05 +00:00
|
|
|
* matches the input format of multiSet. Returns a `Promise` object.
|
2015-03-11 19:06:03 +00:00
|
|
|
*
|
|
|
|
* multiGet(['k1', 'k2'], cb) -> cb([['k1', 'val1'], ['k2', 'val2']])
|
2016-03-15 00:37:34 +00:00
|
|
|
*
|
|
|
|
* Example:
|
|
|
|
* ```javascript
|
|
|
|
* AsyncStorage.getAllKeys((err, keys) => {
|
|
|
|
* AsyncStorage.multiGet(keys, (err, stores) => {
|
|
|
|
* stores.map((result, i, store) => {
|
|
|
|
* // get at each store's key/value so you can work with it
|
|
|
|
* let key = store[i][0];
|
|
|
|
* let value = store[i][1];
|
|
|
|
* });
|
|
|
|
* });
|
|
|
|
* });
|
|
|
|
* ```
|
2015-03-11 19:06:03 +00:00
|
|
|
*/
|
|
|
|
multiGet: function(
|
|
|
|
keys: Array<string>,
|
2015-05-02 01:29:43 +00:00
|
|
|
callback?: ?(errors: ?Array<Error>, result: ?Array<Array<string>>) => void
|
2015-04-07 09:02:05 +00:00
|
|
|
): Promise {
|
2015-12-03 16:08:37 +00:00
|
|
|
if (!this._immediate) {
|
|
|
|
this._immediate = setImmediate(() => {
|
|
|
|
this._immediate = null;
|
|
|
|
this.flushGetRequests();
|
2015-04-07 09:02:05 +00:00
|
|
|
});
|
2015-12-03 16:08:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var getRequest = {
|
|
|
|
keys: keys,
|
|
|
|
callback: callback,
|
2016-02-06 00:44:44 +00:00
|
|
|
// do we need this?
|
2015-12-03 16:08:37 +00:00
|
|
|
keyIndex: this._getKeys.length,
|
|
|
|
resolve: null,
|
|
|
|
reject: null,
|
|
|
|
};
|
|
|
|
|
|
|
|
var promiseResult = new Promise((resolve, reject) => {
|
|
|
|
getRequest.resolve = resolve;
|
|
|
|
getRequest.reject = reject;
|
2015-03-11 19:06:03 +00:00
|
|
|
});
|
2015-12-03 16:08:37 +00:00
|
|
|
|
|
|
|
this._getRequests.push(getRequest);
|
2016-02-06 00:44:44 +00:00
|
|
|
// avoid fetching duplicates
|
|
|
|
keys.forEach(key => {
|
|
|
|
if (this._getKeys.indexOf(key) === -1) {
|
|
|
|
this._getKeys.push(key);
|
|
|
|
}
|
|
|
|
});
|
2015-12-03 16:08:37 +00:00
|
|
|
|
|
|
|
return promiseResult;
|
2015-03-11 19:06:03 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* multiSet and multiMerge take arrays of key-value array pairs that match
|
2015-09-16 17:30:53 +00:00
|
|
|
* the output of multiGet, e.g. Returns a `Promise` object.
|
2015-03-11 19:06:03 +00:00
|
|
|
*
|
|
|
|
* multiSet([['k1', 'val1'], ['k2', 'val2']], cb);
|
2016-03-15 00:37:34 +00:00
|
|
|
*
|
|
|
|
* Example: see multiMerge for an example
|
2015-03-11 19:06:03 +00:00
|
|
|
*/
|
|
|
|
multiSet: function(
|
|
|
|
keyValuePairs: Array<Array<string>>,
|
2015-05-02 01:29:43 +00:00
|
|
|
callback?: ?(errors: ?Array<Error>) => void
|
2015-04-07 09:02:05 +00:00
|
|
|
): Promise {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
RCTAsyncStorage.multiSet(keyValuePairs, function(errors) {
|
2015-10-28 18:59:06 +00:00
|
|
|
var error = convertErrors(errors);
|
2015-04-07 09:02:05 +00:00
|
|
|
callback && callback(error);
|
2015-10-28 18:59:06 +00:00
|
|
|
if (error) {
|
2015-04-07 09:02:05 +00:00
|
|
|
reject(error);
|
|
|
|
} else {
|
|
|
|
resolve(null);
|
|
|
|
}
|
|
|
|
});
|
2015-03-11 19:06:03 +00:00
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2015-04-07 09:02:05 +00:00
|
|
|
* Delete all the keys in the `keys` array. Returns a `Promise` object.
|
2016-03-15 00:37:34 +00:00
|
|
|
*
|
|
|
|
* Example:
|
|
|
|
* ```javascript
|
|
|
|
* let keys = ['k1', 'k2'];
|
|
|
|
* AsyncStorage.multiRemove(keys, (err) => {
|
|
|
|
* // keys k1 & k2 removed, if they existed
|
|
|
|
* // do most stuff after removal (if you want)
|
|
|
|
* });
|
|
|
|
* ```
|
2015-03-11 19:06:03 +00:00
|
|
|
*/
|
|
|
|
multiRemove: function(
|
|
|
|
keys: Array<string>,
|
2015-05-02 01:29:43 +00:00
|
|
|
callback?: ?(errors: ?Array<Error>) => void
|
2015-04-07 09:02:05 +00:00
|
|
|
): Promise {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
RCTAsyncStorage.multiRemove(keys, function(errors) {
|
2015-10-28 18:59:06 +00:00
|
|
|
var error = convertErrors(errors);
|
2015-04-07 09:02:05 +00:00
|
|
|
callback && callback(error);
|
2015-10-28 18:59:06 +00:00
|
|
|
if (error) {
|
2015-04-07 09:02:05 +00:00
|
|
|
reject(error);
|
|
|
|
} else {
|
|
|
|
resolve(null);
|
|
|
|
}
|
|
|
|
});
|
2015-03-11 19:06:03 +00:00
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Merges existing values with input values, assuming they are stringified
|
2015-04-07 09:02:05 +00:00
|
|
|
* json. Returns a `Promise` object.
|
2015-03-11 19:06:03 +00:00
|
|
|
*
|
|
|
|
* Not supported by all native implementations.
|
2016-03-15 00:37:34 +00:00
|
|
|
*
|
|
|
|
* Example:
|
|
|
|
* ```javascript
|
|
|
|
// first user, initial values
|
|
|
|
* let UID234_object = {
|
|
|
|
* name: 'Chris',
|
|
|
|
* age: 30,
|
|
|
|
* traits: {hair: 'brown', eyes: 'brown'},
|
|
|
|
* };
|
|
|
|
|
|
|
|
* // first user, delta values
|
|
|
|
* let UID234_delta = {
|
|
|
|
* age: 31,
|
|
|
|
* traits: {eyes: 'blue', shoe_size: 10},
|
|
|
|
* };
|
|
|
|
|
|
|
|
* // second user, initial values
|
|
|
|
* let UID345_object = {
|
|
|
|
* name: 'Marge',
|
|
|
|
* age: 25,
|
|
|
|
* traits: {hair: 'blonde', eyes: 'blue'},
|
|
|
|
* };
|
|
|
|
|
|
|
|
* // second user, delta values
|
|
|
|
* let UID345_delta = {
|
|
|
|
* age: 26,
|
|
|
|
* traits: {eyes: 'green', shoe_size: 6},
|
|
|
|
* };
|
|
|
|
|
|
|
|
* let multi_set_pairs = [['UID234', JSON.stringify(UID234_object)], ['UID345', JSON.stringify(UID345_object)]]
|
|
|
|
* let multi_merge_pairs = [['UID234', JSON.stringify(UID234_delta)], ['UID345', JSON.stringify(UID345_delta)]]
|
|
|
|
|
|
|
|
* AsyncStorage.multiSet(multi_set_pairs, (err) => {
|
|
|
|
* AsyncStorage.multiMerge(multi_merge_pairs, (err) => {
|
|
|
|
* AsyncStorage.multiGet(['UID234','UID345'], (err, stores) => {
|
|
|
|
* stores.map( (result, i, store) => {
|
|
|
|
* let key = store[i][0];
|
|
|
|
* let val = store[i][1];
|
|
|
|
* console.log(key, val);
|
|
|
|
* // => UID234 {"name":"Chris","age":31,"traits":{"shoe_size":10,"hair":"brown","eyes":"blue"}}
|
|
|
|
* // => UID345 {"name":"Marge","age":26,"traits":{"shoe_size":6,"hair":"blonde","eyes":"green"}}
|
|
|
|
* });
|
|
|
|
* });
|
|
|
|
* });
|
|
|
|
* });
|
|
|
|
* ```
|
2015-03-11 19:06:03 +00:00
|
|
|
*/
|
|
|
|
multiMerge: function(
|
|
|
|
keyValuePairs: Array<Array<string>>,
|
2015-05-02 01:29:43 +00:00
|
|
|
callback?: ?(errors: ?Array<Error>) => void
|
2015-04-07 09:02:05 +00:00
|
|
|
): Promise {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
RCTAsyncStorage.multiMerge(keyValuePairs, function(errors) {
|
2015-10-28 18:59:06 +00:00
|
|
|
var error = convertErrors(errors);
|
2015-04-07 09:02:05 +00:00
|
|
|
callback && callback(error);
|
2015-10-28 18:59:06 +00:00
|
|
|
if (error) {
|
2015-04-07 09:02:05 +00:00
|
|
|
reject(error);
|
|
|
|
} else {
|
|
|
|
resolve(null);
|
|
|
|
}
|
|
|
|
});
|
2015-03-11 19:06:03 +00:00
|
|
|
});
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
// Not all native implementations support merge.
|
2015-03-17 10:08:46 +00:00
|
|
|
if (!RCTAsyncStorage.multiMerge) {
|
2015-03-11 19:06:03 +00:00
|
|
|
delete AsyncStorage.mergeItem;
|
|
|
|
delete AsyncStorage.multiMerge;
|
|
|
|
}
|
|
|
|
|
2015-10-28 18:59:06 +00:00
|
|
|
function convertErrors(errs) {
|
|
|
|
if (!errs) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
return (Array.isArray(errs) ? errs : [errs]).map((e) => convertError(e));
|
|
|
|
}
|
|
|
|
|
2015-03-11 19:06:03 +00:00
|
|
|
function convertError(error) {
|
|
|
|
if (!error) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
var out = new Error(error.message);
|
|
|
|
out.key = error.key; // flow doesn't like this :(
|
|
|
|
return out;
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = AsyncStorage;
|