Skip to content

Commit

Permalink
feat(firestore): support clearPersistence() & terminate() APIs (inver…
Browse files Browse the repository at this point in the history
…tase#3591)

Co-authored-by: Mike Diarmid <mike.diarmid@gmail.com>

[publish]
  • Loading branch information
russellwheatley authored Jun 22, 2020
1 parent fbed173 commit 30c6d3f
Show file tree
Hide file tree
Showing 10 changed files with 189 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,50 +20,63 @@
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()
);

Expand All @@ -79,7 +92,8 @@ private static void setFirestoreSettings(FirebaseFirestore firebaseFirestore, St

firebaseFirestore.setFirestoreSettings(firestoreSettings.build());

settingsLock.put(appName, true);

preferences.remove(cacheSizeKey).remove(hostKey).remove(persistenceKey).remove(sslKey).apply();
}

static Query getQueryForFirestore(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(":")) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,18 @@
*/

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;

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 {

Expand All @@ -52,35 +53,44 @@ Task<Void> settings(String appName, Map<String, Object> settings) {

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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,17 @@ public void setLogLevel(String logLevel) {
}
}

@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 -> {
Expand Down Expand Up @@ -77,4 +88,15 @@ public void settings(String appName, ReadableMap settings, Promise promise) {
}
});
}

@ReactMethod
public void terminate(String appName, Promise promise) {
module.terminate(appName).addOnCompleteListener(task -> {
if (task.isSuccessful()) {
promise.resolve(null);
} else {
rejectPromiseFirestoreException(promise, task.getException());
}
});
}
}
31 changes: 31 additions & 0 deletions e2e/firestore.e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
});
});
});
1 change: 1 addition & 0 deletions ios/RNFBFirestore/RNFBFirestoreCommon.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
40 changes: 23 additions & 17 deletions ios/RNFBFirestore/RNFBFirestoreCommon.m
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,27 @@
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 {
Expand All @@ -44,16 +57,6 @@ + (dispatch_queue_t)getFirestoreQueue {
}

+ (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];

Expand All @@ -79,9 +82,12 @@ + (void)setFirestoreSettings:(FIRFirestore *)firestore appName:(NSString *)appNa
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; {
Expand Down
32 changes: 32 additions & 0 deletions ios/RNFBFirestore/RNFBFirestoreModule.m
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,36 @@ + (BOOL)requiresMainQueueSetup {
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 changes: 22 additions & 0 deletions lib/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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>;
}
}

Expand Down
Loading

0 comments on commit 30c6d3f

Please sign in to comment.