mirror of
https://github.com/zhigang1992/react-native-firebase.git
synced 2026-04-18 12:12:23 +08:00
feat(firestore): support clearPersistence() & terminate() APIs (#3591)
Co-authored-by: Mike Diarmid <mike.diarmid@gmail.com> [publish]
This commit is contained in:
11453
docs/typedoc.json
vendored
11453
docs/typedoc.json
vendored
File diff suppressed because it is too large
Load Diff
2
docs/typedoc.min.json
vendored
2
docs/typedoc.min.json
vendored
File diff suppressed because one or more lines are too long
@@ -76,6 +76,10 @@ public class UniversalFirebasePreferences {
|
||||
getPreferences().edit().clear().apply();
|
||||
}
|
||||
|
||||
public SharedPreferences.Editor remove(String key){
|
||||
return getPreferences().edit().remove(key);
|
||||
}
|
||||
|
||||
private SharedPreferences getPreferences() {
|
||||
if (preferences == null) {
|
||||
preferences = ReactNativeFirebaseApp
|
||||
|
||||
@@ -37,6 +37,8 @@
|
||||
|
||||
- (void)clearAll;
|
||||
|
||||
- (void)remove:(NSString *)key;
|
||||
|
||||
+ (RNFBPreferences *)shared;
|
||||
|
||||
@end
|
||||
|
||||
@@ -83,6 +83,10 @@ static RNFBPreferences *sharedInstance;
|
||||
[_userDefaults removePersistentDomainForName:RNFBDomainIdentifier];
|
||||
}
|
||||
|
||||
- (void)remove:(NSString *)key {
|
||||
[_userDefaults removeObjectForKey:key];
|
||||
}
|
||||
|
||||
+ (RNFBPreferences *)shared {
|
||||
return sharedInstance;
|
||||
}
|
||||
|
||||
@@ -20,50 +20,63 @@ package io.invertase.firebase.firestore;
|
||||
import com.google.firebase.FirebaseApp;
|
||||
import com.google.firebase.firestore.DocumentReference;
|
||||
import com.google.firebase.firestore.FirebaseFirestore;
|
||||
import com.google.firebase.firestore.FirebaseFirestoreException;
|
||||
import com.google.firebase.firestore.FirebaseFirestoreSettings;
|
||||
import com.google.firebase.firestore.Query;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.WeakHashMap;
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
import io.invertase.firebase.common.UniversalFirebasePreferences;
|
||||
|
||||
public class UniversalFirebaseFirestoreCommon {
|
||||
private static HashMap<String, Boolean> settingsLock = new HashMap<>();
|
||||
static WeakHashMap<String, WeakReference<FirebaseFirestore>> instanceCache = new WeakHashMap<>();
|
||||
|
||||
static FirebaseFirestore getFirestoreForApp(String appName) {
|
||||
WeakReference<FirebaseFirestore> cachedInstance = instanceCache.get(appName);
|
||||
|
||||
if(cachedInstance != null){
|
||||
return cachedInstance.get();
|
||||
}
|
||||
|
||||
FirebaseApp firebaseApp = FirebaseApp.getInstance(appName);
|
||||
|
||||
FirebaseFirestore instance = FirebaseFirestore.getInstance(firebaseApp);
|
||||
|
||||
setFirestoreSettings(instance, appName);
|
||||
|
||||
instanceCache.put(appName, new WeakReference<FirebaseFirestore>(instance));
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
private static void setFirestoreSettings(FirebaseFirestore firebaseFirestore, String appName) {
|
||||
// Ensure not already been set
|
||||
if (settingsLock.containsKey(appName)) return;
|
||||
|
||||
UniversalFirebasePreferences preferences = UniversalFirebasePreferences.getSharedInstance();
|
||||
FirebaseFirestoreSettings.Builder firestoreSettings = new FirebaseFirestoreSettings.Builder();
|
||||
|
||||
String cacheSizeKey = UniversalFirebaseFirestoreStatics.FIRESTORE_CACHE_SIZE + "_" + appName;
|
||||
String hostKey = UniversalFirebaseFirestoreStatics.FIRESTORE_HOST + "_" + appName;
|
||||
String persistenceKey = UniversalFirebaseFirestoreStatics.FIRESTORE_PERSISTENCE + "_" + appName;
|
||||
String sslKey = UniversalFirebaseFirestoreStatics.FIRESTORE_SSL + "_" + appName;
|
||||
|
||||
|
||||
int cacheSizeBytes = preferences.getIntValue(
|
||||
UniversalFirebaseFirestoreStatics.FIRESTORE_CACHE_SIZE + "_" + appName,
|
||||
cacheSizeKey,
|
||||
(int) firebaseFirestore.getFirestoreSettings().getCacheSizeBytes()
|
||||
);
|
||||
|
||||
String host = preferences.getStringValue(
|
||||
UniversalFirebaseFirestoreStatics.FIRESTORE_HOST + "_" + appName,
|
||||
hostKey,
|
||||
firebaseFirestore.getFirestoreSettings().getHost()
|
||||
);
|
||||
|
||||
boolean persistence = preferences.getBooleanValue(
|
||||
UniversalFirebaseFirestoreStatics.FIRESTORE_PERSISTENCE + "_" + appName,
|
||||
persistenceKey,
|
||||
firebaseFirestore.getFirestoreSettings().isPersistenceEnabled()
|
||||
);
|
||||
|
||||
boolean ssl = preferences.getBooleanValue(
|
||||
UniversalFirebaseFirestoreStatics.FIRESTORE_SSL + "_" + appName,
|
||||
sslKey,
|
||||
firebaseFirestore.getFirestoreSettings().isSslEnabled()
|
||||
);
|
||||
|
||||
@@ -79,7 +92,8 @@ public class UniversalFirebaseFirestoreCommon {
|
||||
|
||||
firebaseFirestore.setFirestoreSettings(firestoreSettings.build());
|
||||
|
||||
settingsLock.put(appName, true);
|
||||
|
||||
preferences.remove(cacheSizeKey).remove(hostKey).remove(persistenceKey).remove(sslKey).apply();
|
||||
}
|
||||
|
||||
static Query getQueryForFirestore(
|
||||
|
||||
@@ -30,7 +30,7 @@ public class UniversalFirebaseFirestoreException extends Exception {
|
||||
UniversalFirebaseFirestoreException(FirebaseFirestoreException nativeException, Throwable cause) {
|
||||
super(nativeException != null ? nativeException.getMessage() : "", cause);
|
||||
|
||||
String code = "unknown";
|
||||
String code = null;
|
||||
String message = "An unknown error occurred";
|
||||
|
||||
if (cause != null && cause.getMessage() != null && cause.getMessage().contains(":")) {
|
||||
|
||||
@@ -18,10 +18,10 @@ package io.invertase.firebase.firestore;
|
||||
*/
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.google.android.gms.tasks.Task;
|
||||
import com.google.android.gms.tasks.Tasks;
|
||||
import com.google.firebase.firestore.FirebaseFirestore;
|
||||
import com.google.firebase.firestore.FirebaseFirestoreSettings;
|
||||
import io.invertase.firebase.common.UniversalFirebaseModule;
|
||||
import io.invertase.firebase.common.UniversalFirebasePreferences;
|
||||
|
||||
@@ -29,6 +29,7 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import static io.invertase.firebase.firestore.UniversalFirebaseFirestoreCommon.getFirestoreForApp;
|
||||
import static io.invertase.firebase.firestore.UniversalFirebaseFirestoreCommon.instanceCache;
|
||||
|
||||
public class UniversalFirebaseFirestoreModule extends UniversalFirebaseModule {
|
||||
|
||||
@@ -52,35 +53,44 @@ public class UniversalFirebaseFirestoreModule extends UniversalFirebaseModule {
|
||||
|
||||
UniversalFirebasePreferences.getSharedInstance().setIntValue(
|
||||
UniversalFirebaseFirestoreStatics.FIRESTORE_CACHE_SIZE + "_" + appName,
|
||||
Objects.requireNonNull(cacheSizeBytesDouble).intValue()
|
||||
);
|
||||
Objects.requireNonNull(cacheSizeBytesDouble).intValue());
|
||||
}
|
||||
|
||||
// settings.host
|
||||
if (settings.containsKey("host")) {
|
||||
UniversalFirebasePreferences.getSharedInstance().setStringValue(
|
||||
UniversalFirebaseFirestoreStatics.FIRESTORE_HOST + "_" + appName,
|
||||
(String) settings.get("host")
|
||||
);
|
||||
UniversalFirebaseFirestoreStatics.FIRESTORE_HOST + "_" + appName, (String) settings.get("host"));
|
||||
}
|
||||
|
||||
// settings.persistence
|
||||
if (settings.containsKey("persistence")) {
|
||||
UniversalFirebasePreferences.getSharedInstance().setBooleanValue(
|
||||
UniversalFirebaseFirestoreStatics.FIRESTORE_PERSISTENCE + "_" + appName,
|
||||
(boolean) settings.get("persistence")
|
||||
);
|
||||
(boolean) settings.get("persistence"));
|
||||
}
|
||||
|
||||
// settings.ssl
|
||||
if (settings.containsKey("ssl")) {
|
||||
UniversalFirebasePreferences.getSharedInstance().setBooleanValue(
|
||||
UniversalFirebaseFirestoreStatics.FIRESTORE_SSL + "_" + appName,
|
||||
(boolean) settings.get("ssl")
|
||||
);
|
||||
UniversalFirebaseFirestoreStatics.FIRESTORE_SSL + "_" + appName, (boolean) settings.get("ssl"));
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
Task<Void> clearPersistence(String appName) {
|
||||
return getFirestoreForApp(appName).clearPersistence();
|
||||
}
|
||||
|
||||
Task<Void> terminate(String appName) {
|
||||
FirebaseFirestore firebaseFirestore = getFirestoreForApp(appName);
|
||||
|
||||
if (instanceCache.get(appName) != null) {
|
||||
instanceCache.get(appName).clear();
|
||||
instanceCache.remove(appName);
|
||||
}
|
||||
|
||||
return firebaseFirestore.terminate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,17 @@ public class ReactNativeFirebaseFirestoreModule extends ReactNativeFirebaseModul
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void clearPersistence(String appName, Promise promise) {
|
||||
module.clearPersistence(appName).addOnCompleteListener(task -> {
|
||||
if (task.isSuccessful()) {
|
||||
promise.resolve(null);
|
||||
} else {
|
||||
rejectPromiseFirestoreException(promise, task.getException());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void disableNetwork(String appName, Promise promise) {
|
||||
module.disableNetwork(appName).addOnCompleteListener(task -> {
|
||||
@@ -77,4 +88,15 @@ public class ReactNativeFirebaseFirestoreModule extends ReactNativeFirebaseModul
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void terminate(String appName, Promise promise) {
|
||||
module.terminate(appName).addOnCompleteListener(task -> {
|
||||
if (task.isSuccessful()) {
|
||||
promise.resolve(null);
|
||||
} else {
|
||||
rejectPromiseFirestoreException(promise, task.getException());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -318,4 +318,35 @@ describe('firestore()', () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('Clear cached data persistence', () => {
|
||||
it('should clear any cached data', async () => {
|
||||
const db = firebase.firestore();
|
||||
const id = 'foobar';
|
||||
const ref = db.doc(`v6/${id}`);
|
||||
await ref.set({ foo: 'bar' });
|
||||
|
||||
try {
|
||||
await db.clearPersistence();
|
||||
return Promise.reject(new Error('Did not throw an Error.'));
|
||||
} catch (error) {
|
||||
error.code.should.equal('firestore/failed-precondition');
|
||||
}
|
||||
|
||||
const doc = await ref.get({ source: 'cache' });
|
||||
|
||||
should(doc.id).equal(id);
|
||||
|
||||
await db.terminate();
|
||||
await db.clearPersistence();
|
||||
|
||||
try {
|
||||
await ref.get({ source: 'cache' });
|
||||
return Promise.reject(new Error('Did not throw an Error.'));
|
||||
} catch (error) {
|
||||
error.code.should.equal('firestore/unavailable');
|
||||
return Promise.resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -41,3 +41,4 @@ extern NSString *const FIRESTORE_CACHE_SIZE;
|
||||
extern NSString *const FIRESTORE_HOST;
|
||||
extern NSString *const FIRESTORE_PERSISTENCE;
|
||||
extern NSString *const FIRESTORE_SSL;
|
||||
extern NSMutableDictionary * instanceCache;
|
||||
|
||||
@@ -24,14 +24,27 @@ NSString *const FIRESTORE_HOST = @"firebase_firestore_host";
|
||||
NSString *const FIRESTORE_PERSISTENCE = @"firebase_firestore_persistence";
|
||||
NSString *const FIRESTORE_SSL = @"firebase_firestore_ssl";
|
||||
|
||||
__strong NSMutableDictionary *settingsLock;
|
||||
NSMutableDictionary * instanceCache;
|
||||
|
||||
@implementation RNFBFirestoreCommon
|
||||
|
||||
+ (FIRFirestore *)getFirestoreForApp:(FIRApp *)app {
|
||||
FIRFirestore *instance = [FIRFirestore firestoreForApp:app];
|
||||
[self setFirestoreSettings:instance appName:[RNFBSharedUtils getAppJavaScriptName:app.name]];
|
||||
return instance;
|
||||
if(instanceCache == nil){
|
||||
instanceCache = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
|
||||
FIRFirestore * cachedInstance = instanceCache[[app name]];
|
||||
|
||||
if(cachedInstance){
|
||||
return cachedInstance;
|
||||
}
|
||||
|
||||
FIRFirestore *instance = [FIRFirestore firestoreForApp:app];
|
||||
|
||||
[self setFirestoreSettings:instance appName:[RNFBSharedUtils getAppJavaScriptName:app.name]];
|
||||
|
||||
instanceCache[[app name]] = instance;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
+ (dispatch_queue_t)getFirestoreQueue {
|
||||
@@ -44,16 +57,6 @@ __strong NSMutableDictionary *settingsLock;
|
||||
}
|
||||
|
||||
+ (void)setFirestoreSettings:(FIRFirestore *)firestore appName:(NSString *)appName {
|
||||
@synchronized(settingsLock) {
|
||||
if (settingsLock == nil) {
|
||||
settingsLock = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
|
||||
// Prevent setting if already set
|
||||
if (settingsLock[appName]) {
|
||||
return;
|
||||
}
|
||||
|
||||
FIRFirestoreSettings *firestoreSettings = [[FIRFirestoreSettings alloc] init];
|
||||
RNFBPreferences *preferences = [RNFBPreferences shared];
|
||||
|
||||
@@ -79,9 +82,12 @@ __strong NSMutableDictionary *settingsLock;
|
||||
NSString *sslKey = [NSString stringWithFormat:@"%@_%@", FIRESTORE_SSL, appName];
|
||||
firestoreSettings.sslEnabled = (BOOL) [preferences getBooleanValue:sslKey defaultValue:firestore.settings.sslEnabled];
|
||||
|
||||
settingsLock[appName] = @(YES);
|
||||
firestore.settings = firestoreSettings;
|
||||
}
|
||||
|
||||
[preferences remove:cacheKey];
|
||||
[preferences remove:hostKey];
|
||||
[preferences remove:persistenceKey];
|
||||
[preferences remove:sslKey];
|
||||
}
|
||||
|
||||
+ (FIRDocumentReference *)getDocumentForFirestore:(FIRFirestore *)firestore path:(NSString *)path; {
|
||||
|
||||
@@ -103,4 +103,36 @@ RCT_EXPORT_METHOD(settings:
|
||||
resolve([NSNull null]);
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(clearPersistence:
|
||||
(FIRApp *) firebaseApp
|
||||
: (RCTPromiseResolveBlock) resolve
|
||||
: (RCTPromiseRejectBlock)reject
|
||||
) {
|
||||
[[RNFBFirestoreCommon getFirestoreForApp:firebaseApp] clearPersistenceWithCompletion:^(NSError *error) {
|
||||
if (error) {
|
||||
[RNFBFirestoreCommon promiseRejectFirestoreException:reject error:error];
|
||||
} else {
|
||||
resolve(nil);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(terminate:
|
||||
(FIRApp *) firebaseApp
|
||||
: (RCTPromiseResolveBlock) resolve
|
||||
: (RCTPromiseRejectBlock)reject
|
||||
) {
|
||||
FIRFirestore *instance = [RNFBFirestoreCommon getFirestoreForApp:firebaseApp];
|
||||
|
||||
[instance terminateWithCompletion:^(NSError *error) {
|
||||
if (error) {
|
||||
[RNFBFirestoreCommon promiseRejectFirestoreException:reject error:error];
|
||||
} else {
|
||||
[instanceCache removeObjectForKey: [firebaseApp name]];
|
||||
resolve(nil);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
|
||||
22
packages/firestore/lib/index.d.ts
vendored
22
packages/firestore/lib/index.d.ts
vendored
@@ -1958,6 +1958,28 @@ export namespace FirebaseFirestoreTypes {
|
||||
* @param settings A `Settings` object.
|
||||
*/
|
||||
settings(settings: Settings): Promise<void>;
|
||||
/**
|
||||
* Aimed primarily at clearing up any data cached from running tests. Needs to be executed before any database calls
|
||||
* are made.
|
||||
*
|
||||
* #### Example
|
||||
*
|
||||
*```js
|
||||
* await firebase.firestore().clearPersistence();
|
||||
* ```
|
||||
*/
|
||||
clearPersistence(): Promise<void>;
|
||||
/**
|
||||
* Typically called to ensure a new Firestore instance is initialized before calling
|
||||
* `firebase.firestore().clearPersistence()`.
|
||||
*
|
||||
* #### Example
|
||||
*
|
||||
*```js
|
||||
* await firebase.firestore().terminate();
|
||||
* ```
|
||||
*/
|
||||
terminate(): Promise<void>;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -80,6 +80,14 @@ class FirebaseFirestoreModule extends FirebaseModule {
|
||||
return new FirestoreWriteBatch(this);
|
||||
}
|
||||
|
||||
async clearPersistence() {
|
||||
await this.native.clearPersistence();
|
||||
}
|
||||
|
||||
async terminate() {
|
||||
await this.native.terminate();
|
||||
}
|
||||
|
||||
collection(collectionPath) {
|
||||
if (!isString(collectionPath)) {
|
||||
throw new Error(
|
||||
@@ -130,8 +138,8 @@ class FirebaseFirestoreModule extends FirebaseModule {
|
||||
);
|
||||
}
|
||||
|
||||
disableNetwork() {
|
||||
return this.native.disableNetwork();
|
||||
async disableNetwork() {
|
||||
await this.native.disableNetwork();
|
||||
}
|
||||
|
||||
doc(documentPath) {
|
||||
@@ -152,8 +160,8 @@ class FirebaseFirestoreModule extends FirebaseModule {
|
||||
return new FirestoreDocumentReference(this, path);
|
||||
}
|
||||
|
||||
enableNetwork() {
|
||||
return this.native.enableNetwork();
|
||||
async enableNetwork() {
|
||||
await this.native.enableNetwork();
|
||||
}
|
||||
|
||||
runTransaction(updateFunction) {
|
||||
|
||||
Reference in New Issue
Block a user