Merge pull request #457 from invertase/firestore

Initial Firestore funtionality
This commit is contained in:
chrisbianca 2017-10-03 17:28:24 +01:00 committed by GitHub
commit 1fe91cd788
69 changed files with 6287 additions and 684 deletions

View File

@ -5,6 +5,7 @@
[![Package Quality](http://npm.packagequality.com/shield/react-native-firebase.svg?style=flat-square)](http://packagequality.com/#?package=react-native-firebase)
[![Chat](https://img.shields.io/badge/chat-on%20discord-7289da.svg?style=flat-square)](https://discord.gg/t6bdqMs)
[![Donate](https://img.shields.io/badge/Donate-Patreon-green.svg?style=flat-square)](https://www.patreon.com/invertase)
[![Twitter Follow](https://img.shields.io/twitter/follow/rnfirebase.svg?style=social&label=Follow)](https://twitter.com/rnfirebase)
**RNFirebase** makes using [Firebase](http://firebase.com) with React Native simple.
@ -57,6 +58,7 @@ All in all, RNFirebase provides much faster performance (~2x) over the web SDK a
| **Cloud Messaging (FCM)** | ✅ | ✅ | ✅ |**?**|
| **Crash Reporting** | ✅ | ✅ | ✅ | ❌ |
| **Dynamic Links** | ❌ | ❌ | ❌ | ❌ |
| **Firestore** | ❌ | ❌ | ✅ | ❌ |
| **Invites** | ❌ | ❌ | ❌ | ❌ |
| **Performance Monitoring** | ✅ | ✅ | ✅ | ❌ |
| **Realtime Database** | ✅ | ✅ | ✅ | ✅ |
@ -68,18 +70,13 @@ All in all, RNFirebase provides much faster performance (~2x) over the web SDK a
---
### Supported versions - React Native / Firebase
> The table below shows the supported version of `react-native-firebase` for different React Native versions
> The table below shows the supported versions of React Native and the Firebase SDKs for different versions of `react-native-firebase`
| | v0.36 - v0.39 | v0.40 - v0.46 | v0.47 +
| ------------------------------- | :---: | :---: | :---: |
| react-native-firebase | 1.X.X | 2.X.X | 2.1.X |
> The table below shows the minimum supported versions of the Firebase SDKs for each version of `react-native-firebase`
| | v1 | v2 | v3 |
| ---------------------- | :---: | :---: | :---: |
| Firebase Android SDK | 10.2.0+ | 11.0.0 + | 11.2.0 + |
| Firebase iOS SDK | 3.15.0+ | 4.0.0 + | 4.0.0 + |
| | 1.X.X | 2.0.X | 2.1.X / 2.2.X | 3.0.X |
|------------------------|-------------|-------------|-----------------|----------|
| React Native | 0.36 - 0.39 | 0.40 - 0.46 | 0.47 + | 0.48 + |
| Firebase Android SDK | 10.2.0 + | 11.0.0 + | 11.0.0 + | 11.4.2 + |
| Firebase iOS SDK | 3.15.0 + | 4.0.0 + | 4.0.0 + | 4.3.0 + |
---

View File

@ -1,5 +1,5 @@
buildscript {
ext.firebaseVersion = '11.2.0'
ext.firebaseVersion = '11.4.2'
repositories {
jcenter()
}
@ -33,6 +33,10 @@ android {
allprojects {
repositories {
jcenter()
mavenLocal()
maven {
url "https://maven.google.com"
}
}
}
@ -85,4 +89,5 @@ dependencies {
compile "com.google.firebase:firebase-crash:$firebaseVersion"
compile "com.google.firebase:firebase-perf:$firebaseVersion"
compile "com.google.firebase:firebase-ads:$firebaseVersion"
compile "com.google.firebase:firebase-firestore:$firebaseVersion"
}

View File

@ -0,0 +1,27 @@
package io.invertase.firebase;
public class ErrorUtils {
/**
* Wrap a message string with the specified service name e.g. 'Database'
*
* @param message
* @param service
* @param fullCode
* @return
*/
public static String getMessageWithService(String message, String service, String fullCode) {
// Service: Error message (service/code).
return service + ": " + message + " (" + fullCode.toLowerCase() + ").";
}
/**
* Generate a service error code string e.g. 'DATABASE/PERMISSION-DENIED'
*
* @param service
* @param code
* @return
*/
public static String getCodeWithService(String service, String code) {
return service.toLowerCase() + "/" + code.toLowerCase();
}
}

View File

@ -31,18 +31,6 @@ public class RNFirebasePackage implements ReactPackage {
return modules;
}
/**
* @return list of JS modules to register with the newly created catalyst instance.
* <p/>
* IMPORTANT: Note that only modules that needs to be accessible from the native code should be
* listed here. Also listing a native module here doesn't imply that the JS implementation of it
* will be automatically included in the JS bundle.
*/
// TODO: Removed in 0.47.0. Here for backwards compatability
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}
/**
* @param reactContext
* @return a list of view managers that should be registered with {@link UIManagerModule}

View File

@ -78,7 +78,6 @@ public class Utils {
/**
* @param dataSnapshot
* @param registration
* @param previousChildName
* @return
*/

View File

@ -29,18 +29,6 @@ public class RNFirebaseAdMobPackage implements ReactPackage {
return modules;
}
/**
* @return list of JS modules to register with the newly created catalyst instance.
* <p/>
* IMPORTANT: Note that only modules that needs to be accessible from the native code should be
* listed here. Also listing a native module here doesn't imply that the JS implementation of it
* will be automatically included in the JS bundle.
*/
// TODO: Removed in 0.47.0. Here for backwards compatability
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}
/**
* @param reactContext
* @return a list of view managers that should be registered with {@link UIManagerModule}

View File

@ -33,18 +33,6 @@ public class RNFirebaseAnalyticsPackage implements ReactPackage {
return modules;
}
/**
* @return list of JS modules to register with the newly created catalyst instance.
* <p/>
* IMPORTANT: Note that only modules that needs to be accessible from the native code should be
* listed here. Also listing a native module here doesn't imply that the JS implementation of it
* will be automatically included in the JS bundle.
*/
// TODO: Removed in 0.47.0. Here for backwards compatability
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}
/**
* @param reactContext
* @return a list of view managers that should be registered with {@link UIManagerModule}

View File

@ -28,18 +28,6 @@ public class RNFirebaseAuthPackage implements ReactPackage {
return modules;
}
/**
* @return list of JS modules to register with the newly created catalyst instance.
* <p/>
* IMPORTANT: Note that only modules that needs to be accessible from the native code should be
* listed here. Also listing a native module here doesn't imply that the JS implementation of it
* will be automatically included in the JS bundle.
*/
// TODO: Removed in 0.47.0. Here for backwards compatability
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}
/**
* @param reactContext
* @return a list of view managers that should be registered with {@link UIManagerModule}

View File

@ -28,18 +28,6 @@ public class RNFirebaseRemoteConfigPackage implements ReactPackage {
return modules;
}
/**
* @return list of JS modules to register with the newly created catalyst instance.
* <p/>
* IMPORTANT: Note that only modules that needs to be accessible from the native code should be
* listed here. Also listing a native module here doesn't imply that the JS implementation of it
* will be automatically included in the JS bundle.
*/
// TODO: Removed in 0.47.0. Here for backwards compatability
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}
/**
* @param reactContext
* @return a list of view managers that should be registered with {@link UIManagerModule}

View File

@ -28,18 +28,6 @@ public class RNFirebaseCrashPackage implements ReactPackage {
return modules;
}
/**
* @return list of JS modules to register with the newly created catalyst instance.
* <p/>
* IMPORTANT: Note that only modules that needs to be accessible from the native code should be
* listed here. Also listing a native module here doesn't imply that the JS implementation of it
* will be automatically included in the JS bundle.
*/
// TODO: Removed in 0.47.0. Here for backwards compatability
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}
/**
* @param reactContext
* @return a list of view managers that should be registered with {@link UIManagerModule}

View File

@ -26,6 +26,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import io.invertase.firebase.ErrorUtils;
import io.invertase.firebase.Utils;
@ -522,30 +523,6 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
return existingRef;
}
/**
* Wrap a message string with the specified service name e.g. 'Database'
*
* @param message
* @param service
* @param fullCode
* @return
*/
private static String getMessageWithService(String message, String service, String fullCode) {
// Service: Error message (service/code).
return service + ": " + message + " (" + fullCode.toLowerCase() + ").";
}
/**
* Generate a service error code string e.g. 'DATABASE/PERMISSION-DENIED'
*
* @param service
* @param code
* @return
*/
private static String getCodeWithService(String service, String code) {
return service.toLowerCase() + "/" + code.toLowerCase();
}
/**
* Convert as firebase DatabaseError instance into a writable map
* with the correct web-like error codes.
@ -564,56 +541,56 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
switch (nativeError.getCode()) {
case DatabaseError.DATA_STALE:
code = getCodeWithService(service, "data-stale");
message = getMessageWithService("The transaction needs to be run again with current data.", service, code);
code = ErrorUtils.getCodeWithService(service, "data-stale");
message = ErrorUtils.getMessageWithService("The transaction needs to be run again with current data.", service, code);
break;
case DatabaseError.OPERATION_FAILED:
code = getCodeWithService(service, "failure");
message = getMessageWithService("The server indicated that this operation failed.", service, code);
code = ErrorUtils.getCodeWithService(service, "failure");
message = ErrorUtils.getMessageWithService("The server indicated that this operation failed.", service, code);
break;
case DatabaseError.PERMISSION_DENIED:
code = getCodeWithService(service, "permission-denied");
message = getMessageWithService("Client doesn't have permission to access the desired data.", service, code);
code = ErrorUtils.getCodeWithService(service, "permission-denied");
message = ErrorUtils.getMessageWithService("Client doesn't have permission to access the desired data.", service, code);
break;
case DatabaseError.DISCONNECTED:
code = getCodeWithService(service, "disconnected");
message = getMessageWithService("The operation had to be aborted due to a network disconnect.", service, code);
code = ErrorUtils.getCodeWithService(service, "disconnected");
message = ErrorUtils.getMessageWithService("The operation had to be aborted due to a network disconnect.", service, code);
break;
case DatabaseError.EXPIRED_TOKEN:
code = getCodeWithService(service, "expired-token");
message = getMessageWithService("The supplied auth token has expired.", service, code);
code = ErrorUtils.getCodeWithService(service, "expired-token");
message = ErrorUtils.getMessageWithService("The supplied auth token has expired.", service, code);
break;
case DatabaseError.INVALID_TOKEN:
code = getCodeWithService(service, "invalid-token");
message = getMessageWithService("The supplied auth token was invalid.", service, code);
code = ErrorUtils.getCodeWithService(service, "invalid-token");
message = ErrorUtils.getMessageWithService("The supplied auth token was invalid.", service, code);
break;
case DatabaseError.MAX_RETRIES:
code = getCodeWithService(service, "max-retries");
message = getMessageWithService("The transaction had too many retries.", service, code);
code = ErrorUtils.getCodeWithService(service, "max-retries");
message = ErrorUtils.getMessageWithService("The transaction had too many retries.", service, code);
break;
case DatabaseError.OVERRIDDEN_BY_SET:
code = getCodeWithService(service, "overridden-by-set");
message = getMessageWithService("The transaction was overridden by a subsequent set.", service, code);
code = ErrorUtils.getCodeWithService(service, "overridden-by-set");
message = ErrorUtils.getMessageWithService("The transaction was overridden by a subsequent set.", service, code);
break;
case DatabaseError.UNAVAILABLE:
code = getCodeWithService(service, "unavailable");
message = getMessageWithService("The service is unavailable.", service, code);
code = ErrorUtils.getCodeWithService(service, "unavailable");
message = ErrorUtils.getMessageWithService("The service is unavailable.", service, code);
break;
case DatabaseError.USER_CODE_EXCEPTION:
code = getCodeWithService(service, "user-code-exception");
message = getMessageWithService("User code called from the Firebase Database runloop threw an exception.", service, code);
code = ErrorUtils.getCodeWithService(service, "user-code-exception");
message = ErrorUtils.getMessageWithService("User code called from the Firebase Database runloop threw an exception.", service, code);
break;
case DatabaseError.NETWORK_ERROR:
code = getCodeWithService(service, "network-error");
message = getMessageWithService("The operation could not be performed due to a network error.", service, code);
code = ErrorUtils.getCodeWithService(service, "network-error");
message = ErrorUtils.getMessageWithService("The operation could not be performed due to a network error.", service, code);
break;
case DatabaseError.WRITE_CANCELED:
code = getCodeWithService(service, "write-cancelled");
message = getMessageWithService("The write was canceled by the user.", service, code);
code = ErrorUtils.getCodeWithService(service, "write-cancelled");
message = ErrorUtils.getMessageWithService("The write was canceled by the user.", service, code);
break;
default:
code = getCodeWithService(service, "unknown");
message = getMessageWithService("An unknown error occurred.", service, code);
code = ErrorUtils.getCodeWithService(service, "unknown");
message = ErrorUtils.getMessageWithService("An unknown error occurred.", service, code);
}
errorMap.putString("code", code);

View File

@ -28,18 +28,6 @@ public class RNFirebaseDatabasePackage implements ReactPackage {
return modules;
}
/**
* @return list of JS modules to register with the newly created catalyst instance.
* <p/>
* IMPORTANT: Note that only modules that needs to be accessible from the native code should be
* listed here. Also listing a native module here doesn't imply that the JS implementation of it
* will be automatically included in the JS bundle.
*/
// TODO: Removed in 0.47.0. Here for backwards compatability
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}
/**
* @param reactContext
* @return a list of view managers that should be registered with {@link UIManagerModule}

View File

@ -29,8 +29,8 @@ class RNFirebaseDatabaseReference {
private String appName;
private ReactContext reactContext;
private static final String TAG = "RNFirebaseDBReference";
private HashMap<String, ChildEventListener> childEventListeners;
private HashMap<String, ValueEventListener> valueEventListeners;
private HashMap<String, ChildEventListener> childEventListeners = new HashMap<>();
private HashMap<String, ValueEventListener> valueEventListeners = new HashMap<>();
/**
* RNFirebase wrapper around FirebaseDatabaseReference,
@ -47,8 +47,6 @@ class RNFirebaseDatabaseReference {
query = null;
appName = app;
reactContext = context;
childEventListeners = new HashMap<>();
valueEventListeners = new HashMap<>();
buildDatabaseQueryAtPathAndModifiers(refPath, modifiersArray);
}

View File

@ -0,0 +1,197 @@
package io.invertase.firebase.firestore;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.google.firebase.firestore.DocumentChange;
import com.google.firebase.firestore.DocumentSnapshot;
import com.google.firebase.firestore.QuerySnapshot;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
public class FirestoreSerialize {
private static final String KEY_CHANGES = "changes";
private static final String KEY_DATA = "data";
private static final String KEY_DOC_CHANGE_DOCUMENT = "document";
private static final String KEY_DOC_CHANGE_NEW_INDEX = "newIndex";
private static final String KEY_DOC_CHANGE_OLD_INDEX = "oldIndex";
private static final String KEY_DOC_CHANGE_TYPE = "type";
private static final String KEY_DOCUMENTS = "documents";
private static final String KEY_PATH = "path";
/**
* Convert a DocumentSnapshot instance into a React Native WritableMap
*
* @param documentSnapshot DocumentSnapshot
* @return WritableMap
*/
static WritableMap snapshotToWritableMap(DocumentSnapshot documentSnapshot) {
WritableMap documentMap = Arguments.createMap();
documentMap.putString(KEY_PATH, documentSnapshot.getReference().getPath());
if (documentSnapshot.exists()) {
documentMap.putMap(KEY_DATA, objectMapToWritable(documentSnapshot.getData()));
}
// Missing fields from web SDK
// createTime
// readTime
// updateTime
return documentMap;
}
public static WritableMap snapshotToWritableMap(QuerySnapshot querySnapshot) {
WritableMap queryMap = Arguments.createMap();
List<DocumentChange> documentChanges = querySnapshot.getDocumentChanges();
queryMap.putArray(KEY_CHANGES, documentChangesToWritableArray(documentChanges));
// documents
WritableArray documents = Arguments.createArray();
List<DocumentSnapshot> documentSnapshots = querySnapshot.getDocuments();
for (DocumentSnapshot documentSnapshot : documentSnapshots) {
documents.pushMap(snapshotToWritableMap(documentSnapshot));
}
queryMap.putArray(KEY_DOCUMENTS, documents);
return queryMap;
}
/**
* Convert a List of DocumentChange instances into a React Native WritableArray
*
* @param documentChanges List<DocumentChange>
* @return WritableArray
*/
static WritableArray documentChangesToWritableArray(List<DocumentChange> documentChanges) {
WritableArray documentChangesWritable = Arguments.createArray();
for (DocumentChange documentChange : documentChanges) {
documentChangesWritable.pushMap(documentChangeToWritableMap(documentChange));
}
return documentChangesWritable;
}
/**
* Convert a DocumentChange instance into a React Native WritableMap
*
* @param documentChange DocumentChange
* @return WritableMap
*/
static WritableMap documentChangeToWritableMap(DocumentChange documentChange) {
WritableMap documentChangeMap = Arguments.createMap();
switch (documentChange.getType()) {
case ADDED:
documentChangeMap.putString(KEY_DOC_CHANGE_TYPE, "added");
break;
case REMOVED:
documentChangeMap.putString(KEY_DOC_CHANGE_TYPE, "removed");
break;
case MODIFIED:
documentChangeMap.putString(KEY_DOC_CHANGE_TYPE, "modified");
}
documentChangeMap.putMap(KEY_DOC_CHANGE_DOCUMENT,
snapshotToWritableMap(documentChange.getDocument()));
documentChangeMap.putInt(KEY_DOC_CHANGE_NEW_INDEX, documentChange.getNewIndex());
documentChangeMap.putInt(KEY_DOC_CHANGE_OLD_INDEX, documentChange.getOldIndex());
return documentChangeMap;
}
/**
* Converts an Object Map into a React Native WritableMap.
*
* @param map Map<String, Object>
* @return WritableMap
*/
static WritableMap objectMapToWritable(Map<String, Object> map) {
WritableMap writableMap = Arguments.createMap();
for (Map.Entry<String, Object> entry : map.entrySet()) {
putValue(writableMap, entry.getKey(), entry.getValue());
}
return writableMap;
}
/**
* Converts an Object array into a React Native WritableArray.
*
* @param array Object[]
* @return WritableArray
*/
static WritableArray objectArrayToWritable(Object[] array) {
WritableArray writableArray = Arguments.createArray();
for (Object item : array) {
if (item == null) {
writableArray.pushNull();
continue;
}
Class itemClass = item.getClass();
if (itemClass == Boolean.class) {
writableArray.pushBoolean((Boolean) item);
} else if (itemClass == Integer.class) {
writableArray.pushDouble(((Integer) item).doubleValue());
} else if (itemClass == Double.class) {
writableArray.pushDouble((Double) item);
} else if (itemClass == Float.class) {
writableArray.pushDouble(((Float) item).doubleValue());
} else if (itemClass == String.class) {
writableArray.pushString(item.toString());
} else if (itemClass == Map.class) {
writableArray.pushMap((objectMapToWritable((Map<String, Object>) item)));
} else if (itemClass == Arrays.class) {
writableArray.pushArray(objectArrayToWritable((Object[]) item));
} else if (itemClass == List.class) {
List<Object> list = (List<Object>) item;
Object[] listAsArray = list.toArray(new Object[list.size()]);
writableArray.pushArray(objectArrayToWritable(listAsArray));
} else {
throw new RuntimeException("Cannot convert object of type " + item);
}
}
return writableArray;
}
/**
* Detects an objects type and calls the relevant WritableMap setter method to add the value.
*
* @param map WritableMap
* @param key String
* @param value Object
*/
static void putValue(WritableMap map, String key, Object value) {
if (value == null) {
map.putNull(key);
} else {
Class valueClass = value.getClass();
if (valueClass == Boolean.class) {
map.putBoolean(key, (Boolean) value);
} else if (valueClass == Integer.class) {
map.putDouble(key, ((Integer) value).doubleValue());
} else if (valueClass == Double.class) {
map.putDouble(key, (Double) value);
} else if (valueClass == Float.class) {
map.putDouble(key, ((Float) value).doubleValue());
} else if (valueClass == String.class) {
map.putString(key, value.toString());
} else if (valueClass == Map.class) {
map.putMap(key, (objectMapToWritable((Map<String, Object>) value)));
} else if (valueClass == Arrays.class) {
map.putArray(key, objectArrayToWritable((Object[]) value));
} else if (valueClass == List.class) {
List<Object> list = (List<Object>) value;
Object[] array = list.toArray(new Object[list.size()]);
map.putArray(key, objectArrayToWritable(array));
} else {
throw new RuntimeException("Cannot convert object of type " + value);
}
}
}
}

View File

@ -0,0 +1,342 @@
package io.invertase.firebase.firestore;
import android.support.annotation.NonNull;
import android.util.Log;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.FirebaseApp;
import com.google.firebase.firestore.DocumentReference;
import com.google.firebase.firestore.FieldValue;
import com.google.firebase.firestore.FirebaseFirestore;
import com.google.firebase.firestore.FirebaseFirestoreException;
import com.google.firebase.firestore.SetOptions;
import com.google.firebase.firestore.WriteBatch;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import io.invertase.firebase.ErrorUtils;
import io.invertase.firebase.Utils;
public class RNFirebaseFirestore extends ReactContextBaseJavaModule {
private static final String TAG = "RNFirebaseFirestore";
// private SparseArray<RNFirebaseTransactionHandler> transactionHandlers = new SparseArray<>();
RNFirebaseFirestore(ReactApplicationContext reactContext) {
super(reactContext);
}
/*
* REACT NATIVE METHODS
*/
@ReactMethod
public void collectionGet(String appName, String path, ReadableArray filters,
ReadableArray orders, ReadableMap options, final Promise promise) {
RNFirebaseFirestoreCollectionReference ref = getCollectionForAppPath(appName, path, filters, orders, options);
ref.get(promise);
}
@ReactMethod
public void collectionOffSnapshot(String appName, String path, ReadableArray filters,
ReadableArray orders, ReadableMap options, String listenerId) {
RNFirebaseFirestoreCollectionReference.offSnapshot(listenerId);
}
@ReactMethod
public void collectionOnSnapshot(String appName, String path, ReadableArray filters,
ReadableArray orders, ReadableMap options, String listenerId) {
RNFirebaseFirestoreCollectionReference ref = getCollectionForAppPath(appName, path, filters, orders, options);
ref.onSnapshot(listenerId);
}
@ReactMethod
public void documentBatch(final String appName, final ReadableArray writes,
final ReadableMap commitOptions, final Promise promise) {
FirebaseFirestore firestore = getFirestoreForApp(appName);
WriteBatch batch = firestore.batch();
final List<Object> writesArray = Utils.recursivelyDeconstructReadableArray(writes);
for (Object w : writesArray) {
Map<String, Object> write = (Map) w;
String type = (String) write.get("type");
String path = (String) write.get("path");
Map<String, Object> data = (Map) write.get("data");
DocumentReference ref = firestore.document(path);
switch (type) {
case "DELETE":
batch = batch.delete(ref);
break;
case "SET":
Map<String, Object> options = (Map) write.get("options");
if (options != null && options.containsKey("merge") && (boolean)options.get("merge")) {
batch = batch.set(ref, data, SetOptions.merge());
} else {
batch = batch.set(ref, data);
}
break;
case "UPDATE":
batch = batch.update(ref, data);
break;
}
}
batch.commit().addOnCompleteListener(new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull Task<Void> task) {
if (task.isSuccessful()) {
Log.d(TAG, "set:onComplete:success");
WritableArray result = Arguments.createArray();
for (Object w : writesArray) {
// Missing fields from web SDK
// writeTime
result.pushMap(Arguments.createMap());
}
promise.resolve(result);
} else {
Log.e(TAG, "set:onComplete:failure", task.getException());
RNFirebaseFirestore.promiseRejectException(promise, (FirebaseFirestoreException)task.getException());
}
}
});
}
@ReactMethod
public void documentCollections(String appName, String path, final Promise promise) {
RNFirebaseFirestoreDocumentReference ref = getDocumentForAppPath(appName, path);
ref.collections(promise);
}
@ReactMethod
public void documentCreate(String appName, String path, ReadableMap data, final Promise promise) {
RNFirebaseFirestoreDocumentReference ref = getDocumentForAppPath(appName, path);
ref.create(data, promise);
}
@ReactMethod
public void documentDelete(String appName, String path, ReadableMap options, final Promise promise) {
RNFirebaseFirestoreDocumentReference ref = getDocumentForAppPath(appName, path);
ref.delete(options, promise);
}
@ReactMethod
public void documentGet(String appName, String path, final Promise promise) {
RNFirebaseFirestoreDocumentReference ref = getDocumentForAppPath(appName, path);
ref.get(promise);
}
@ReactMethod
public void documentGetAll(String appName, ReadableArray documents, final Promise promise) {
// Not supported on Android out of the box
}
@ReactMethod
public void documentOffSnapshot(String appName, String path, String listenerId) {
RNFirebaseFirestoreDocumentReference.offSnapshot(listenerId);
}
@ReactMethod
public void documentOnSnapshot(String appName, String path, String listenerId) {
RNFirebaseFirestoreDocumentReference ref = getDocumentForAppPath(appName, path);
ref.onSnapshot(listenerId);
}
@ReactMethod
public void documentSet(String appName, String path, ReadableMap data, ReadableMap options, final Promise promise) {
RNFirebaseFirestoreDocumentReference ref = getDocumentForAppPath(appName, path);
ref.set(data, options, promise);
}
@ReactMethod
public void documentUpdate(String appName, String path, ReadableMap data, final Promise promise) {
RNFirebaseFirestoreDocumentReference ref = getDocumentForAppPath(appName, path);
ref.update(data, promise);
}
/*
* INTERNALS/UTILS
*/
/**
* Generates a js-like error from an exception and rejects the provided promise with it.
*
* @param exception Exception Exception normally from a task result.
* @param promise Promise react native promise
*/
static void promiseRejectException(Promise promise, FirebaseFirestoreException exception) {
WritableMap jsError = getJSError(exception);
promise.reject(
jsError.getString("code"),
jsError.getString("message"),
exception
);
}
/**
* Get a database instance for a specific firebase app instance
*
* @param appName
* @return
*/
static FirebaseFirestore getFirestoreForApp(String appName) {
FirebaseApp firebaseApp = FirebaseApp.getInstance(appName);
return FirebaseFirestore.getInstance(firebaseApp);
}
/**
* Get a collection reference for a specific app and path
*
* @param appName
* @param filters
* @param orders
* @param options
* @param path @return
*/
private RNFirebaseFirestoreCollectionReference getCollectionForAppPath(String appName, String path,
ReadableArray filters,
ReadableArray orders,
ReadableMap options) {
return new RNFirebaseFirestoreCollectionReference(this.getReactApplicationContext(), appName, path, filters, orders, options);
}
/**
* Get a document reference for a specific app and path
*
* @param appName
* @param path
* @return
*/
private RNFirebaseFirestoreDocumentReference getDocumentForAppPath(String appName, String path) {
return new RNFirebaseFirestoreDocumentReference(this.getReactApplicationContext(), appName, path);
}
/**
* Convert as firebase DatabaseError instance into a writable map
* with the correct web-like error codes.
*
* @param nativeException
* @return
*/
static WritableMap getJSError(FirebaseFirestoreException nativeException) {
WritableMap errorMap = Arguments.createMap();
errorMap.putInt("nativeErrorCode", nativeException.getCode().value());
errorMap.putString("nativeErrorMessage", nativeException.getMessage());
String code;
String message;
String service = "Firestore";
// TODO: Proper error mappings
switch (nativeException.getCode()) {
case OK:
code = ErrorUtils.getCodeWithService(service, "ok");
message = ErrorUtils.getMessageWithService("Ok.", service, code);
break;
case CANCELLED:
code = ErrorUtils.getCodeWithService(service, "cancelled");
message = ErrorUtils.getMessageWithService("Cancelled.", service, code);
break;
case UNKNOWN:
code = ErrorUtils.getCodeWithService(service, "unknown");
message = ErrorUtils.getMessageWithService("An unknown error occurred.", service, code);
break;
case INVALID_ARGUMENT:
code = ErrorUtils.getCodeWithService(service, "invalid-argument");
message = ErrorUtils.getMessageWithService("Invalid argument.", service, code);
break;
case NOT_FOUND:
code = ErrorUtils.getCodeWithService(service, "not-found");
message = ErrorUtils.getMessageWithService("Not found.", service, code);
break;
case ALREADY_EXISTS:
code = ErrorUtils.getCodeWithService(service, "already-exists");
message = ErrorUtils.getMessageWithService("Already exists.", service, code);
break;
case PERMISSION_DENIED:
code = ErrorUtils.getCodeWithService(service, "permission-denied");
message = ErrorUtils.getMessageWithService("Permission denied.", service, code);
break;
case RESOURCE_EXHAUSTED:
code = ErrorUtils.getCodeWithService(service, "resource-exhausted");
message = ErrorUtils.getMessageWithService("Resource exhausted.", service, code);
break;
case FAILED_PRECONDITION:
code = ErrorUtils.getCodeWithService(service, "failed-precondition");
message = ErrorUtils.getMessageWithService("Failed precondition.", service, code);
break;
case ABORTED:
code = ErrorUtils.getCodeWithService(service, "aborted");
message = ErrorUtils.getMessageWithService("Aborted.", service, code);
break;
case OUT_OF_RANGE:
code = ErrorUtils.getCodeWithService(service, "out-of-range");
message = ErrorUtils.getMessageWithService("Out of range.", service, code);
break;
case UNIMPLEMENTED:
code = ErrorUtils.getCodeWithService(service, "unimplemented");
message = ErrorUtils.getMessageWithService("Unimplemented.", service, code);
break;
case INTERNAL:
code = ErrorUtils.getCodeWithService(service, "internal");
message = ErrorUtils.getMessageWithService("Internal.", service, code);
break;
case UNAVAILABLE:
code = ErrorUtils.getCodeWithService(service, "unavailable");
message = ErrorUtils.getMessageWithService("Unavailable.", service, code);
break;
case DATA_LOSS:
code = ErrorUtils.getCodeWithService(service, "data-loss");
message = ErrorUtils.getMessageWithService("Data loss.", service, code);
break;
case UNAUTHENTICATED:
code = ErrorUtils.getCodeWithService(service, "unauthenticated");
message = ErrorUtils.getMessageWithService("Unauthenticated.", service, code);
break;
default:
code = ErrorUtils.getCodeWithService(service, "unknown");
message = ErrorUtils.getMessageWithService("An unknown error occurred.", service, code);
}
errorMap.putString("code", code);
errorMap.putString("message", message);
return errorMap;
}
/**
* React Method - returns this module name
*
* @return
*/
@Override
public String getName() {
return "RNFirebaseFirestore";
}
/**
* React Native constants for RNFirebaseFirestore
*
* @return
*/
@Override
public Map<String, Object> getConstants() {
final Map<String, Object> constants = new HashMap<>();
constants.put("deleteFieldValue", FieldValue.delete().toString());
constants.put("serverTimestampFieldValue", FieldValue.serverTimestamp().toString());
return constants;
}
}

View File

@ -0,0 +1,218 @@
package io.invertase.firebase.firestore;
import android.support.annotation.NonNull;
import android.util.Log;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.firestore.EventListener;
import com.google.firebase.firestore.FirebaseFirestoreException;
import com.google.firebase.firestore.ListenerRegistration;
import com.google.firebase.firestore.Query;
import com.google.firebase.firestore.QuerySnapshot;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import io.invertase.firebase.Utils;
public class RNFirebaseFirestoreCollectionReference {
private static final String TAG = "RNFSCollectionReference";
private static Map<String, ListenerRegistration> collectionSnapshotListeners = new HashMap<>();
private final String appName;
private final String path;
private final ReadableArray filters;
private final ReadableArray orders;
private final ReadableMap options;
private final Query query;
private ReactContext reactContext;
RNFirebaseFirestoreCollectionReference(ReactContext reactContext, String appName, String path,
ReadableArray filters, ReadableArray orders,
ReadableMap options) {
this.appName = appName;
this.path = path;
this.filters = filters;
this.orders = orders;
this.options = options;
this.query = buildQuery();
this.reactContext = reactContext;
}
void get(final Promise promise) {
query.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
@Override
public void onComplete(@NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
Log.d(TAG, "get:onComplete:success");
WritableMap data = FirestoreSerialize.snapshotToWritableMap(task.getResult());
promise.resolve(data);
} else {
Log.e(TAG, "get:onComplete:failure", task.getException());
RNFirebaseFirestore.promiseRejectException(promise, (FirebaseFirestoreException)task.getException());
}
}
});
}
public static void offSnapshot(final String listenerId) {
ListenerRegistration listenerRegistration = collectionSnapshotListeners.remove(listenerId);
if (listenerRegistration != null) {
listenerRegistration.remove();
}
}
public void onSnapshot(final String listenerId) {
if (!collectionSnapshotListeners.containsKey(listenerId)) {
final EventListener<QuerySnapshot> listener = new EventListener<QuerySnapshot>() {
@Override
public void onEvent(QuerySnapshot querySnapshot, FirebaseFirestoreException exception) {
if (exception == null) {
handleQuerySnapshotEvent(listenerId, querySnapshot);
} else {
ListenerRegistration listenerRegistration = collectionSnapshotListeners.remove(listenerId);
if (listenerRegistration != null) {
listenerRegistration.remove();
}
handleQuerySnapshotError(listenerId, exception);
}
}
};
ListenerRegistration listenerRegistration = this.query.addSnapshotListener(listener);
collectionSnapshotListeners.put(listenerId, listenerRegistration);
}
}
/*
* INTERNALS/UTILS
*/
boolean hasListeners() {
return !collectionSnapshotListeners.isEmpty();
}
private Query buildQuery() {
Query query = RNFirebaseFirestore.getFirestoreForApp(appName).collection(path);
query = applyFilters(query);
query = applyOrders(query);
query = applyOptions(query);
return query;
}
private Query applyFilters(Query query) {
List<Object> filtersList = Utils.recursivelyDeconstructReadableArray(filters);
for (Object f : filtersList) {
Map<String, Object> filter = (Map) f;
String fieldPath = (String) filter.get("fieldPath");
String operator = (String) filter.get("operator");
Object value = filter.get("value");
switch (operator) {
case "EQUAL":
query = query.whereEqualTo(fieldPath, value);
break;
case "GREATER_THAN":
query = query.whereGreaterThan(fieldPath, value);
break;
case "GREATER_THAN_OR_EQUAL":
query = query.whereGreaterThanOrEqualTo(fieldPath, value);
break;
case "LESS_THAN":
query = query.whereLessThan(fieldPath, value);
break;
case "LESS_THAN_OR_EQUAL":
query = query.whereLessThanOrEqualTo(fieldPath, value);
break;
}
}
return query;
}
private Query applyOrders(Query query) {
List<Object> ordersList = Utils.recursivelyDeconstructReadableArray(orders);
for (Object o : ordersList) {
Map<String, Object> order = (Map) o;
String direction = (String) order.get("direction");
String fieldPath = (String) order.get("fieldPath");
query = query.orderBy(fieldPath, Query.Direction.valueOf(direction));
}
return query;
}
private Query applyOptions(Query query) {
if (options.hasKey("endAt")) {
ReadableArray endAtArray = options.getArray("endAt");
query = query.endAt(Utils.recursivelyDeconstructReadableArray(endAtArray));
}
if (options.hasKey("endBefore")) {
ReadableArray endBeforeArray = options.getArray("endBefore");
query = query.endBefore(Utils.recursivelyDeconstructReadableArray(endBeforeArray));
}
if (options.hasKey("limit")) {
int limit = options.getInt("limit");
query = query.limit(limit);
}
if (options.hasKey("offset")) {
// Android doesn't support offset
}
if (options.hasKey("selectFields")) {
// Android doesn't support selectFields
}
if (options.hasKey("startAfter")) {
ReadableArray startAfterArray = options.getArray("startAfter");
query = query.startAfter(Utils.recursivelyDeconstructReadableArray(startAfterArray));
}
if (options.hasKey("startAt")) {
ReadableArray startAtArray = options.getArray("startAt");
query = query.startAt(Utils.recursivelyDeconstructReadableArray(startAtArray));
}
return query;
}
/**
* Handles documentSnapshot events.
*
* @param listenerId
* @param querySnapshot
*/
private void handleQuerySnapshotEvent(String listenerId, QuerySnapshot querySnapshot) {
WritableMap event = Arguments.createMap();
WritableMap data = FirestoreSerialize.snapshotToWritableMap(querySnapshot);
event.putString("appName", appName);
event.putString("path", path);
event.putString("listenerId", listenerId);
event.putMap("querySnapshot", data);
Utils.sendEvent(reactContext, "firestore_collection_sync_event", event);
}
/**
* Handles a documentSnapshot error event
*
* @param listenerId
* @param exception
*/
private void handleQuerySnapshotError(String listenerId, FirebaseFirestoreException exception) {
WritableMap event = Arguments.createMap();
event.putString("appName", appName);
event.putString("path", path);
event.putString("listenerId", listenerId);
event.putMap("error", RNFirebaseFirestore.getJSError(exception));
Utils.sendEvent(reactContext, "firestore_collection_sync_event", event);
}
}

View File

@ -0,0 +1,196 @@
package io.invertase.firebase.firestore;
import android.support.annotation.NonNull;
import android.util.Log;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.firestore.DocumentReference;
import com.google.firebase.firestore.DocumentSnapshot;
import com.google.firebase.firestore.EventListener;
import com.google.firebase.firestore.FirebaseFirestoreException;
import com.google.firebase.firestore.ListenerRegistration;
import com.google.firebase.firestore.SetOptions;
import java.util.HashMap;
import java.util.Map;
import io.invertase.firebase.Utils;
public class RNFirebaseFirestoreDocumentReference {
private static final String TAG = "RNFBFSDocumentReference";
private static Map<String, ListenerRegistration> documentSnapshotListeners = new HashMap<>();
private final String appName;
private final String path;
private ReactContext reactContext;
private final DocumentReference ref;
RNFirebaseFirestoreDocumentReference(ReactContext reactContext, String appName, String path) {
this.appName = appName;
this.path = path;
this.reactContext = reactContext;
this.ref = RNFirebaseFirestore.getFirestoreForApp(appName).document(path);
}
public void collections(Promise promise) {
// Not supported on Android
}
public void create(ReadableMap data, Promise promise) {
// Not supported on Android out of the box
}
public void delete(final ReadableMap options, final Promise promise) {
this.ref.delete().addOnCompleteListener(new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull Task<Void> task) {
if (task.isSuccessful()) {
Log.d(TAG, "delete:onComplete:success");
// Missing fields from web SDK
// writeTime
promise.resolve(Arguments.createMap());
} else {
Log.e(TAG, "delete:onComplete:failure", task.getException());
RNFirebaseFirestore.promiseRejectException(promise, (FirebaseFirestoreException)task.getException());
}
}
});
}
void get(final Promise promise) {
this.ref.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
@Override
public void onComplete(@NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()) {
Log.d(TAG, "get:onComplete:success");
WritableMap data = FirestoreSerialize.snapshotToWritableMap(task.getResult());
promise.resolve(data);
} else {
Log.e(TAG, "get:onComplete:failure", task.getException());
RNFirebaseFirestore.promiseRejectException(promise, (FirebaseFirestoreException)task.getException());
}
}
});
}
public static void offSnapshot(final String listenerId) {
ListenerRegistration listenerRegistration = documentSnapshotListeners.remove(listenerId);
if (listenerRegistration != null) {
listenerRegistration.remove();
}
}
public void onSnapshot(final String listenerId) {
if (!documentSnapshotListeners.containsKey(listenerId)) {
final EventListener<DocumentSnapshot> listener = new EventListener<DocumentSnapshot>() {
@Override
public void onEvent(DocumentSnapshot documentSnapshot, FirebaseFirestoreException exception) {
if (exception == null) {
handleDocumentSnapshotEvent(listenerId, documentSnapshot);
} else {
ListenerRegistration listenerRegistration = documentSnapshotListeners.remove(listenerId);
if (listenerRegistration != null) {
listenerRegistration.remove();
}
handleDocumentSnapshotError(listenerId, exception);
}
}
};
ListenerRegistration listenerRegistration = this.ref.addSnapshotListener(listener);
documentSnapshotListeners.put(listenerId, listenerRegistration);
}
}
public void set(final ReadableMap data, final ReadableMap options, final Promise promise) {
Map<String, Object> map = Utils.recursivelyDeconstructReadableMap(data);
Task<Void> task;
SetOptions setOptions = null;
if (options != null && options.hasKey("merge") && options.getBoolean("merge")) {
task = this.ref.set(map, SetOptions.merge());
} else {
task = this.ref.set(map);
}
task.addOnCompleteListener(new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull Task<Void> task) {
if (task.isSuccessful()) {
Log.d(TAG, "set:onComplete:success");
// Missing fields from web SDK
// writeTime
promise.resolve(Arguments.createMap());
} else {
Log.e(TAG, "set:onComplete:failure", task.getException());
RNFirebaseFirestore.promiseRejectException(promise, (FirebaseFirestoreException)task.getException());
}
}
});
}
public void update(final ReadableMap data, final Promise promise) {
Map<String, Object> map = Utils.recursivelyDeconstructReadableMap(data);
this.ref.update(map).addOnCompleteListener(new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull Task<Void> task) {
if (task.isSuccessful()) {
Log.d(TAG, "update:onComplete:success");
// Missing fields from web SDK
// writeTime
promise.resolve(Arguments.createMap());
} else {
Log.e(TAG, "update:onComplete:failure", task.getException());
RNFirebaseFirestore.promiseRejectException(promise, (FirebaseFirestoreException)task.getException());
}
}
});
}
/*
* INTERNALS/UTILS
*/
boolean hasListeners() {
return !documentSnapshotListeners.isEmpty();
}
/**
* Handles documentSnapshot events.
*
* @param listenerId
* @param documentSnapshot
*/
private void handleDocumentSnapshotEvent(String listenerId, DocumentSnapshot documentSnapshot) {
WritableMap event = Arguments.createMap();
WritableMap data = FirestoreSerialize.snapshotToWritableMap(documentSnapshot);
event.putString("appName", appName);
event.putString("path", path);
event.putString("listenerId", listenerId);
event.putMap("documentSnapshot", data);
Utils.sendEvent(reactContext, "firestore_document_sync_event", event);
}
/**
* Handles a documentSnapshot error event
*
* @param listenerId
* @param exception
*/
private void handleDocumentSnapshotError(String listenerId, FirebaseFirestoreException exception) {
WritableMap event = Arguments.createMap();
event.putString("appName", appName);
event.putString("path", path);
event.putString("listenerId", listenerId);
event.putMap("error", RNFirebaseFirestore.getJSError(exception));
Utils.sendEvent(reactContext, "firestore_document_sync_event", event);
}
}

View File

@ -0,0 +1,39 @@
package io.invertase.firebase.firestore;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@SuppressWarnings("unused")
public class RNFirebaseFirestorePackage implements ReactPackage {
public RNFirebaseFirestorePackage() {
}
/**
* @param reactContext react application context that can be used to create modules
* @return list of native modules to register with the newly created catalyst instance
*/
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new RNFirebaseFirestore(reactContext));
return modules;
}
/**
* @param reactContext
* @return a list of view managers that should be registered with {@link UIManagerModule}
*/
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}

View File

@ -28,18 +28,6 @@ public class RNFirebaseMessagingPackage implements ReactPackage {
return modules;
}
/**
* @return list of JS modules to register with the newly created catalyst instance.
* <p/>
* IMPORTANT: Note that only modules that needs to be accessible from the native code should be
* listed here. Also listing a native module here doesn't imply that the JS implementation of it
* will be automatically included in the JS bundle.
*/
// TODO: Removed in 0.47.0. Here for backwards compatability
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}
/**
* @param reactContext
* @return a list of view managers that should be registered with {@link UIManagerModule}

View File

@ -28,18 +28,6 @@ public class RNFirebasePerformancePackage implements ReactPackage {
return modules;
}
/**
* @return list of JS modules to register with the newly created catalyst instance.
* <p/>
* IMPORTANT: Note that only modules that needs to be accessible from the native code should be
* listed here. Also listing a native module here doesn't imply that the JS implementation of it
* will be automatically included in the JS bundle.
*/
// TODO: Removed in 0.47.0. Here for backwards compatability
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}
/**
* @param reactContext
* @return a list of view managers that should be registered with {@link UIManagerModule}

View File

@ -33,18 +33,6 @@ public class RNFirebaseStoragePackage implements ReactPackage {
return modules;
}
/**
* @return list of JS modules to register with the newly created catalyst instance.
* <p/>
* IMPORTANT: Note that only modules that needs to be accessible from the native code should be
* listed here. Also listing a native module here doesn't imply that the JS implementation of it
* will be automatically included in the JS bundle.
*/
// TODO: Removed in 0.47.0. Here for backwards compatibility
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}
/**
* @param reactContext
* @return a list of view managers that should be registered with {@link UIManagerModule}

View File

@ -11,6 +11,7 @@
[![License](https://img.shields.io/npm/l/react-native-firebase.svg?style=flat-square)](/LICENSE)
[![Chat](https://img.shields.io/badge/chat-on%20discord-7289da.svg?style=flat-square)](https://discord.gg/t6bdqMs)
[![Donate](https://img.shields.io/badge/Donate-Patreon-green.svg?style=flat-square)](https://www.patreon.com/invertase)
[![Twitter Follow](https://img.shields.io/twitter/follow/rnfirebase.svg?style=social&label=Follow)](https://twitter.com/rnfirebase)
</div>
---
@ -46,6 +47,7 @@ All in all, RNFirebase provides much faster performance (~2x) over the web SDK a
| **Cloud Messaging (FCM)** | ✅ | ✅ | ✅ |**?**|
| **Crash Reporting** | ✅ | ✅ | ✅ | ❌ |
| **Dynamic Links** | ❌ | ❌ | ❌ | ❌ |
| **Firestore** | ❌ | ❌ | ✅ | ❌ |
| **Invites** | ❌ | ❌ | ❌ | ❌ |
| **Performance Monitoring** | ✅ | ✅ | ✅ | ❌ |
| **Realtime Database** | ✅ | ✅ | ✅ | ✅ |
@ -57,15 +59,10 @@ All in all, RNFirebase provides much faster performance (~2x) over the web SDK a
---
### Supported versions - React Native / Firebase
> The table below shows the supported version of `react-native-firebase` for different React Native versions
> The table below shows the supported versions of React Native and the Firebase SDKs for different versions of `react-native-firebase`
| | v0.36 - v0.39 | v0.40 - v0.46 | v0.47 +
| ------------------------------- | :---: | :---: | :---: |
| react-native-firebase | 1.X.X | 2.X.X | 2.1.X |
> The table below shows the minimum supported versions of the Firebase SDKs for each version of `react-native-firebase`
| | v1 | v2 | v3 |
| ---------------------- | :---: | :---: | :---: |
| Firebase Android SDK | 10.2.0+ | 11.0.0 + | 11.2.0 + |
| Firebase iOS SDK | 3.15.0+ | 4.0.0 + | 4.0.0 + |
| | 1.X.X | 2.0.X | 2.1.X / 2.2.X | 3.0.X |
|------------------------|-------------|-------------|-----------------|----------|
| React Native | 0.36 - 0.39 | 0.40 - 0.46 | 0.47 + | 0.48 + |
| Firebase Android SDK | 10.2.0 + | 11.0.0 + | 11.0.0 + | 11.4.2 + |
| Firebase iOS SDK | 3.15.0 + | 4.0.0 + | 4.0.0 + | 4.3.0 + |

View File

@ -24,6 +24,7 @@
- [Cloud Messaging](/modules/cloud-messaging)
- [Crash Reporting](/modules/crash)
- [Database](/modules/database)
- [Firestore (Beta)](/modules/firestore)
- [Remote Config](/modules/config)
- [Storage](/modules/storage)
- [Transactions](/modules/transactions)

View File

@ -53,6 +53,7 @@ dependencies {
compile "com.google.firebase:firebase-config:11.2.0"
compile "com.google.firebase:firebase-crash:11.2.0"
compile "com.google.firebase:firebase-database:11.2.0"
compile "com.google.firebase:firebase-firestore:11.2.0"
compile "com.google.firebase:firebase-messaging:11.2.0"
compile "com.google.firebase:firebase-perf:11.2.0"
compile "com.google.firebase:firebase-storage:11.2.0"
@ -88,6 +89,7 @@ import io.invertase.firebase.auth.RNFirebaseAuthPackage; // Firebase Auth
import io.invertase.firebase.config.RNFirebaseRemoteConfigPackage; // Firebase Remote Config
import io.invertase.firebase.crash.RNFirebaseCrashPackage; // Firebase Crash Reporting
import io.invertase.firebase.database.RNFirebaseDatabasePackage; // Firebase Realtime Database
import io.invertase.firebase.firestore.RNFirebaseFirestorePackage; // Firebase Firestore
import io.invertase.firebase.messaging.RNFirebaseMessagingPackage; // Firebase Cloud Messaging
import io.invertase.firebase.perf.RNFirebasePerformancePackage; // Firebase Performance
import io.invertase.firebase.storage.RNFirebaseStoragePackage; // Firebase Storage
@ -107,6 +109,7 @@ public class MainApplication extends Application implements ReactApplication {
new RNFirebaseRemoteConfigPackage(),
new RNFirebaseCrashPackage(),
new RNFirebaseDatabasePackage(),
new RNFirebaseFirestorePackage(),
new RNFirebaseMessagingPackage(),
new RNFirebasePerformancePackage(),
new RNFirebaseStoragePackage()

View File

@ -69,6 +69,7 @@ pod 'Firebase/Auth'
pod 'Firebase/Crash'
pod 'Firebase/Database'
pod 'Firebase/DynamicLinks'
pod 'Firebase/Firestore'
pod 'Firebase/Messaging'
pod 'Firebase/RemoteConfig'
pod 'Firebase/Storage'

View File

@ -2,7 +2,95 @@
## From v2 to v3
<!-- TODO -->
The below is a quick summary of steps to take when migrating from v2 to v3 of RNFirebase. Please see the [v3 change log](https://github.com/invertase/react-native-firebase/releases/tag/v3.0.0) for detailed changes.
** Please note, we're now using `Apache License 2.0` to license this library. **
##### 1) Install the latest version of RNFirebase:
> `npm i react-native-firebase@latest --save`
##### 2) Upgrade react-native version (only if you're currently lower than v0.48):
- Follow the instructions [here](https://facebook.github.io/react-native/docs/upgrading.html)
##### 3) Update your JS code to reflect deprecations/breaking changes:
- ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) **[breaking]** [app] `new RNFirebase()` is no longer supported. See below for information about app initialisation.
- ![#f03c15](https://placehold.it/15/fdfd96/000000?text=+) **[deprecated]** [app] `initializeApp()` for apps that are already initialised natively (i.e. the default app initialised via google-services plist/json) will now log a deprecation warning.
- As these apps are already initialised natively there's no need to call `initializeApp` in your JS code. For now, calling it will just return the app that's already internally initialised - in a future version this will throw an `already initialized` exception.
- Accessing apps can now be done the same way as the web sdk, simply call `firebase.app()` to get the default app, or with the name of specific app as the first arg, e.g. `const meow = firebase.app('catsApp');` to get a specific app.
- ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) **[breaking]** [auth] Third party providers now user `providerId` rather than `provider` as per the Web SDK. If you are manually creating your credentials, you will need to update the field name.
- ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) **[breaking]** [database] Error messages and codes internally re-written to match the web sdk
- ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) **[breaking]** [database] `ref.isEqual` now checks the query modifiers as well as the ref path (was just path before). With the release of multi apps/core support this check now also includes whether the refs are for the same app.
- ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) **[breaking]** [database] on/off behaviour changes. Previous `off` behaviour was incorrect. A `SyncTree/Repo` implementation was added to provide the correct behaviour you'd expect in the web sdk. Whilst this is a breaking change it shouldn't be much of an issue if you've previously setup your on/off handling correctly. See #160 for specifics of this change.
- ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) **[breaking]** [storage] UploadTaskSnapshot -> `downloadUrl` renamed to `downloadURL` to match web sdk
##### 4) Android - Update `android/build.gradle`:
- Check you are using google-services 3.1.0 or greater:
- You must add `maven { url 'https://maven.google.com' }` to your `android/build.gradle` as follows:
```groovy
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.3'
classpath 'com.google.gms:google-services:3.1.0' // CHECK VERSION HERE
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
mavenLocal()
jcenter()
maven {
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
url "$rootDir/../node_modules/react-native/android"
}
// ADD THIS SECTION HERE
maven {
url 'https://maven.google.com'
}
}
}
```
##### 5) Android - Update `app/build.gradle`:
- You must update all your Firebase dependencies to 11.4.2.
##### 6) iOS - Update podfile:
- You need to check that you're running at least version 4.3.0 of the Firebase Pods
- Run `pod outdated`
- Run `pod update`
## From v1 to v2

171
docs/modules/firestore.md Normal file
View File

@ -0,0 +1,171 @@
# Firestore (Beta)
RNFirebase mimics the [Firestore Web SDK](https://firebase.google.com/docs/database/web/read-and-write), whilst
providing support for devices in low/no data connection state.
All Firestore operations are accessed via `firestore()`.
Please note that Persistence (offline support) is enabled by default with Firestore on iOS and Android.
## Add and Manage Data
### Collections
Read information about a collection example:
```javascript
firebase.firestore()
.collection('posts')
.get()
.then(querySnapshot => {
// Access all the documents in the collection
const docs = querySnapshot.docs;
// Access the list of document changes for the collection
const changes = querySnapshot.docChanges;
// Loop through the documents
querySnapshot.forEach((doc) => {
const value = doc.data();
})
})
```
Add to a collection example (generated ID):
```javascript
firebase.firestore()
.collection('posts')
.add({
title: 'Amazing post',
})
.then(() => {
// Document added to collection and ID generated
// Will have path: `posts/{generatedId}`
})
```
Add to a collection example (manual ID):
```javascript
firebase.firestore()
.collection('posts')
.doc('post1')
.set({
title: 'My awesome post',
content: 'Some awesome content',
})
.then(() => {
// Document added to collection with path: `posts/post1`
})
```
### Documents
There are multiple ways to read a document. The following are equivalent examples:
```javascript
firebase.firestore()
.doc('posts/posts1')
.get((documentSnapshot) => {
const value = documentSnapshot.data();
});
firebase.firestore()
.collection('posts')
.doc('posts1')
.get((documentSnapshot) => {
const value = documentSnapshot.data();
});
```
Create a document example:
```javascript
firebase.firestore()
.doc('posts/posts1')
.set({
title: 'My awesome post',
content: 'Some awesome content',
})
.then(() => {
// Document created
});
```
Updating a document example:
```javascript
firebase.firestore()
.doc('posts/posts1')
.update({
title: 'My awesome post',
})
.then(() => {
// Document created
});
```
Deleting a document example:
```javascript
firebase.firestore()
.doc('posts/posts1')
.delete()
.then(() => {
// Document deleted
});
```
### Batching document updates
Writes, updates and deletes to documents can be batched and committed atomically as follows:
```javascript
const ayRef = firebase.firestore().doc('places/AY');
const lRef = firebase.firestore().doc('places/LON');
const nycRef = firebase.firestore().doc('places/NYC');
const sfRef = firebase.firestore().doc('places/SF');
firebase.firestore()
.batch()
.set(ayRef, { name: 'Aylesbury' })
.set(lRef, { name: 'London' })
.set(nycRef, { name: 'New York City' })
.set(sfRef, { name: 'San Francisco' })
.update(nycRef, { population: 1000000 })
.update(sfRef, { name: 'San Fran' })
.set(lRef, { population: 3000000 }, { merge: true })
.delete(ayRef)
.commit()
.then(() => {
// Would end up with three documents in the collection: London, New York City and San Francisco
});
```
### Transactions
Coming soon
## Realtime Updates
### Collections
Listen to collection updates example:
```javascript
firebase.firestore()
.collection('cities')
.where('state', '==', 'CA')
.onSnapshot((querySnapshot) => {
querySnapshot.forEach((doc) => {
// DocumentSnapshot available
})
})
```
The snapshot handler will receive a new query snapshot every time the query results change (that is, when a document is added, removed, or modified).
### Documents
Listen to document updates example:
```javascript
firebase.firestore()
.doc('posts/post1')
.onSnapshot((documentSnapshot) => {
// DocumentSnapshot available
})
```
The snapshot handler will receive the current contents of the document, and any subsequent changes to the document.

View File

@ -12,6 +12,9 @@
8323CF071F6FBD870071420B /* NativeExpressComponent.m in Sources */ = {isa = PBXBuildFile; fileRef = 8323CF011F6FBD870071420B /* NativeExpressComponent.m */; };
8323CF081F6FBD870071420B /* RNFirebaseAdMobBannerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 8323CF031F6FBD870071420B /* RNFirebaseAdMobBannerManager.m */; };
8323CF091F6FBD870071420B /* RNFirebaseAdMobNativeExpressManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 8323CF051F6FBD870071420B /* RNFirebaseAdMobNativeExpressManager.m */; };
8376F7141F7C149100D45A85 /* RNFirebaseFirestoreDocumentReference.m in Sources */ = {isa = PBXBuildFile; fileRef = 8376F70E1F7C149000D45A85 /* RNFirebaseFirestoreDocumentReference.m */; };
8376F7151F7C149100D45A85 /* RNFirebaseFirestore.m in Sources */ = {isa = PBXBuildFile; fileRef = 8376F7101F7C149000D45A85 /* RNFirebaseFirestore.m */; };
8376F7161F7C149100D45A85 /* RNFirebaseFirestoreCollectionReference.m in Sources */ = {isa = PBXBuildFile; fileRef = 8376F7111F7C149000D45A85 /* RNFirebaseFirestoreCollectionReference.m */; };
839D916C1EF3E20B0077C7C8 /* RNFirebaseAdMob.m in Sources */ = {isa = PBXBuildFile; fileRef = 839D914F1EF3E20A0077C7C8 /* RNFirebaseAdMob.m */; };
839D916D1EF3E20B0077C7C8 /* RNFirebaseAdMobInterstitial.m in Sources */ = {isa = PBXBuildFile; fileRef = 839D91511EF3E20A0077C7C8 /* RNFirebaseAdMobInterstitial.m */; };
839D916E1EF3E20B0077C7C8 /* RNFirebaseAdMobRewardedVideo.m in Sources */ = {isa = PBXBuildFile; fileRef = 839D91531EF3E20A0077C7C8 /* RNFirebaseAdMobRewardedVideo.m */; };
@ -50,6 +53,12 @@
8323CF031F6FBD870071420B /* RNFirebaseAdMobBannerManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNFirebaseAdMobBannerManager.m; sourceTree = "<group>"; };
8323CF041F6FBD870071420B /* RNFirebaseAdMobNativeExpressManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNFirebaseAdMobNativeExpressManager.h; sourceTree = "<group>"; };
8323CF051F6FBD870071420B /* RNFirebaseAdMobNativeExpressManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNFirebaseAdMobNativeExpressManager.m; sourceTree = "<group>"; };
8376F70E1F7C149000D45A85 /* RNFirebaseFirestoreDocumentReference.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNFirebaseFirestoreDocumentReference.m; sourceTree = "<group>"; };
8376F70F1F7C149000D45A85 /* RNFirebaseFirestore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNFirebaseFirestore.h; sourceTree = "<group>"; };
8376F7101F7C149000D45A85 /* RNFirebaseFirestore.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNFirebaseFirestore.m; sourceTree = "<group>"; };
8376F7111F7C149000D45A85 /* RNFirebaseFirestoreCollectionReference.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNFirebaseFirestoreCollectionReference.m; sourceTree = "<group>"; };
8376F7121F7C149000D45A85 /* RNFirebaseFirestoreDocumentReference.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNFirebaseFirestoreDocumentReference.h; sourceTree = "<group>"; };
8376F7131F7C149000D45A85 /* RNFirebaseFirestoreCollectionReference.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNFirebaseFirestoreCollectionReference.h; sourceTree = "<group>"; };
839D914E1EF3E20A0077C7C8 /* RNFirebaseAdMob.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNFirebaseAdMob.h; sourceTree = "<group>"; };
839D914F1EF3E20A0077C7C8 /* RNFirebaseAdMob.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNFirebaseAdMob.m; sourceTree = "<group>"; };
839D91501EF3E20A0077C7C8 /* RNFirebaseAdMobInterstitial.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNFirebaseAdMobInterstitial.h; sourceTree = "<group>"; };
@ -105,6 +114,7 @@
839D915A1EF3E20A0077C7C8 /* config */,
839D915D1EF3E20A0077C7C8 /* crash */,
839D91601EF3E20A0077C7C8 /* database */,
8376F70D1F7C141500D45A85 /* firestore */,
839D91631EF3E20A0077C7C8 /* messaging */,
839D91661EF3E20A0077C7C8 /* perf */,
839D91691EF3E20A0077C7C8 /* storage */,
@ -115,6 +125,20 @@
);
sourceTree = "<group>";
};
8376F70D1F7C141500D45A85 /* firestore */ = {
isa = PBXGroup;
children = (
8376F70F1F7C149000D45A85 /* RNFirebaseFirestore.h */,
8376F7101F7C149000D45A85 /* RNFirebaseFirestore.m */,
8376F7131F7C149000D45A85 /* RNFirebaseFirestoreCollectionReference.h */,
8376F7111F7C149000D45A85 /* RNFirebaseFirestoreCollectionReference.m */,
8376F7121F7C149000D45A85 /* RNFirebaseFirestoreDocumentReference.h */,
8376F70E1F7C149000D45A85 /* RNFirebaseFirestoreDocumentReference.m */,
);
name = firestore;
path = RNFirebase/firestore;
sourceTree = "<group>";
};
839D914D1EF3E20A0077C7C8 /* admob */ = {
isa = PBXGroup;
children = (
@ -277,9 +301,12 @@
files = (
839D916E1EF3E20B0077C7C8 /* RNFirebaseAdMobRewardedVideo.m in Sources */,
839D916C1EF3E20B0077C7C8 /* RNFirebaseAdMob.m in Sources */,
8376F7161F7C149100D45A85 /* RNFirebaseFirestoreCollectionReference.m in Sources */,
839D91761EF3E20B0077C7C8 /* RNFirebaseStorage.m in Sources */,
8376F7151F7C149100D45A85 /* RNFirebaseFirestore.m in Sources */,
839D91701EF3E20B0077C7C8 /* RNFirebaseAuth.m in Sources */,
8323CF091F6FBD870071420B /* RNFirebaseAdMobNativeExpressManager.m in Sources */,
8376F7141F7C149100D45A85 /* RNFirebaseFirestoreDocumentReference.m in Sources */,
839D916F1EF3E20B0077C7C8 /* RNFirebaseAnalytics.m in Sources */,
839D91711EF3E20B0077C7C8 /* RNFirebaseRemoteConfig.m in Sources */,
D950369E1D19C77400F7094D /* RNFirebase.m in Sources */,

View File

@ -17,6 +17,10 @@ static NSString *const DATABASE_CHILD_MODIFIED_EVENT = @"child_changed";
static NSString *const DATABASE_CHILD_REMOVED_EVENT = @"child_removed";
static NSString *const DATABASE_CHILD_MOVED_EVENT = @"child_moved";
// Firestore
static NSString *const FIRESTORE_COLLECTION_SYNC_EVENT = @"firestore_collection_sync_event";
static NSString *const FIRESTORE_DOCUMENT_SYNC_EVENT = @"firestore_document_sync_event";
// Storage
static NSString *const STORAGE_EVENT = @"storage_event";
static NSString *const STORAGE_ERROR = @"storage_error";

View File

@ -0,0 +1,26 @@
#ifndef RNFirebaseFirestore_h
#define RNFirebaseFirestore_h
#import <Foundation/Foundation.h>
#if __has_include(<FirebaseFirestore/FirebaseFirestore.h>)
#import <FirebaseFirestore/FirebaseFirestore.h>
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>
@interface RNFirebaseFirestore : RCTEventEmitter <RCTBridgeModule> {}
+ (void)promiseRejectException:(RCTPromiseRejectBlock)reject error:(NSError *)error;
+ (FIRFirestore *)getFirestoreForApp:(NSString *)appName;
+ (NSDictionary *)getJSError:(NSError *)nativeError;
@end
#else
@interface RNFirebaseFirestore : NSObject
@end
#endif
#endif

View File

@ -0,0 +1,223 @@
#import "RNFirebaseFirestore.h"
#if __has_include(<FirebaseFirestore/FirebaseFirestore.h>)
#import <Firebase.h>
#import "RNFirebaseEvents.h"
#import "RNFirebaseFirestoreCollectionReference.h"
#import "RNFirebaseFirestoreDocumentReference.h"
@implementation RNFirebaseFirestore
RCT_EXPORT_MODULE();
- (id)init {
self = [super init];
if (self != nil) {
}
return self;
}
RCT_EXPORT_METHOD(collectionGet:(NSString *) appName
path:(NSString *) path
filters:(NSArray *) filters
orders:(NSArray *) orders
options:(NSDictionary *) options
resolver:(RCTPromiseResolveBlock) resolve
rejecter:(RCTPromiseRejectBlock) reject) {
[[self getCollectionForAppPath:appName path:path filters:filters orders:orders options:options] get:resolve rejecter:reject];
}
RCT_EXPORT_METHOD(collectionOffSnapshot:(NSString *) appName
path:(NSString *) path
filters:(NSArray *) filters
orders:(NSArray *) orders
options:(NSDictionary *) options
listenerId:(nonnull NSString *) listenerId) {
[RNFirebaseFirestoreCollectionReference offSnapshot:listenerId];
}
RCT_EXPORT_METHOD(collectionOnSnapshot:(NSString *) appName
path:(NSString *) path
filters:(NSArray *) filters
orders:(NSArray *) orders
options:(NSDictionary *) options
listenerId:(nonnull NSString *) listenerId) {
RNFirebaseFirestoreCollectionReference *ref = [self getCollectionForAppPath:appName path:path filters:filters orders:orders options:options];
[ref onSnapshot:listenerId];
}
RCT_EXPORT_METHOD(documentBatch:(NSString *) appName
writes:(NSArray *) writes
commitOptions:(NSDictionary *) commitOptions
resolver:(RCTPromiseResolveBlock) resolve
rejecter:(RCTPromiseRejectBlock) reject) {
FIRFirestore *firestore = [RNFirebaseFirestore getFirestoreForApp:appName];
FIRWriteBatch *batch = [firestore batch];
for (NSDictionary *write in writes) {
NSString *type = write[@"type"];
NSString *path = write[@"path"];
NSDictionary *data = write[@"data"];
FIRDocumentReference *ref = [firestore documentWithPath:path];
if ([type isEqualToString:@"DELETE"]) {
batch = [batch deleteDocument:ref];
} else if ([type isEqualToString:@"SET"]) {
NSDictionary *options = write[@"options"];
if (options && options[@"merge"]) {
batch = [batch setData:data forDocument:ref options:[FIRSetOptions merge]];
} else {
batch = [batch setData:data forDocument:ref];
}
} else if ([type isEqualToString:@"UPDATE"]) {
batch = [batch updateData:data forDocument:ref];
}
}
[batch commitWithCompletion:^(NSError * _Nullable error) {
if (error) {
[RNFirebaseFirestore promiseRejectException:reject error:error];
} else {
NSMutableArray *result = [[NSMutableArray alloc] init];
for (NSDictionary *write in writes) {
// Missing fields from web SDK
// writeTime
[result addObject:@{}];
}
resolve(result);
}
}];
}
RCT_EXPORT_METHOD(documentCollections:(NSString *) appName
path:(NSString *) path
resolver:(RCTPromiseResolveBlock) resolve
rejecter:(RCTPromiseRejectBlock) reject) {
[[self getDocumentForAppPath:appName path:path] get:resolve rejecter:reject];
}
RCT_EXPORT_METHOD(documentCreate:(NSString *) appName
path:(NSString *) path
data:(NSDictionary *) data
resolver:(RCTPromiseResolveBlock) resolve
rejecter:(RCTPromiseRejectBlock) reject) {
[[self getDocumentForAppPath:appName path:path] create:data resolver:resolve rejecter:reject];
}
RCT_EXPORT_METHOD(documentDelete:(NSString *) appName
path:(NSString *) path
options:(NSDictionary *) options
resolver:(RCTPromiseResolveBlock) resolve
rejecter:(RCTPromiseRejectBlock) reject) {
[[self getDocumentForAppPath:appName path:path] delete:options resolver:resolve rejecter:reject];
}
RCT_EXPORT_METHOD(documentGet:(NSString *) appName
path:(NSString *) path
resolver:(RCTPromiseResolveBlock) resolve
rejecter:(RCTPromiseRejectBlock) reject) {
[[self getDocumentForAppPath:appName path:path] get:resolve rejecter:reject];
}
RCT_EXPORT_METHOD(documentGetAll:(NSString *) appName
documents:(NSString *) documents
resolver:(RCTPromiseResolveBlock) resolve
rejecter:(RCTPromiseRejectBlock) reject) {
// Not supported on iOS out of the box
}
RCT_EXPORT_METHOD(documentOffSnapshot:(NSString *) appName
path:(NSString *) path
listenerId:(nonnull NSString *) listenerId) {
[RNFirebaseFirestoreDocumentReference offSnapshot:listenerId];
}
RCT_EXPORT_METHOD(documentOnSnapshot:(NSString *) appName
path:(NSString *) path
listenerId:(nonnull NSString *) listenerId) {
RNFirebaseFirestoreDocumentReference *ref = [self getDocumentForAppPath:appName path:path];
[ref onSnapshot:listenerId];
}
RCT_EXPORT_METHOD(documentSet:(NSString *) appName
path:(NSString *) path
data:(NSDictionary *) data
options:(NSDictionary *) options
resolver:(RCTPromiseResolveBlock) resolve
rejecter:(RCTPromiseRejectBlock) reject) {
[[self getDocumentForAppPath:appName path:path] set:data options:options resolver:resolve rejecter:reject];
}
RCT_EXPORT_METHOD(documentUpdate:(NSString *) appName
path:(NSString *) path
data:(NSDictionary *) data
resolver:(RCTPromiseResolveBlock) resolve
rejecter:(RCTPromiseRejectBlock) reject) {
[[self getDocumentForAppPath:appName path:path] update:data resolver:resolve rejecter:reject];
}
/*
* INTERNALS/UTILS
*/
+ (void)promiseRejectException:(RCTPromiseRejectBlock)reject error:(NSError *)error {
NSDictionary *jsError = [RNFirebaseFirestore getJSError:error];
reject([jsError valueForKey:@"code"], [jsError valueForKey:@"message"], error);
}
+ (FIRFirestore *)getFirestoreForApp:(NSString *)appName {
FIRApp *app = [FIRApp appNamed:appName];
return [FIRFirestore firestoreForApp:app];
}
- (RNFirebaseFirestoreCollectionReference *)getCollectionForAppPath:(NSString *)appName path:(NSString *)path filters:(NSArray *)filters orders:(NSArray *)orders options:(NSDictionary *)options {
return [[RNFirebaseFirestoreCollectionReference alloc] initWithPathAndModifiers:self app:appName path:path filters:filters orders:orders options:options];
}
- (RNFirebaseFirestoreDocumentReference *)getDocumentForAppPath:(NSString *)appName path:(NSString *)path {
return [[RNFirebaseFirestoreDocumentReference alloc] initWithPath:self app:appName path:path];
}
// TODO: Move to error util for use in other modules
+ (NSString *)getMessageWithService:(NSString *)message service:(NSString *)service fullCode:(NSString *)fullCode {
return [NSString stringWithFormat:@"%@: %@ (%@).", service, message, [fullCode lowercaseString]];
}
+ (NSString *)getCodeWithService:(NSString *)service code:(NSString *)code {
return [NSString stringWithFormat:@"%@/%@", [service lowercaseString], [code lowercaseString]];
}
+ (NSDictionary *)getJSError:(NSError *)nativeError {
NSMutableDictionary *errorMap = [[NSMutableDictionary alloc] init];
[errorMap setValue:@(nativeError.code) forKey:@"nativeErrorCode"];
[errorMap setValue:[nativeError localizedDescription] forKey:@"nativeErrorMessage"];
NSString *code;
NSString *message;
NSString *service = @"Firestore";
// TODO: Proper error codes
switch (nativeError.code) {
default:
code = [RNFirebaseFirestore getCodeWithService:service code:@"unknown"];
message = [RNFirebaseFirestore getMessageWithService:@"An unknown error occurred." service:service fullCode:code];
break;
}
[errorMap setValue:code forKey:@"code"];
[errorMap setValue:message forKey:@"message"];
return errorMap;
}
- (NSArray<NSString *> *)supportedEvents {
return @[FIRESTORE_COLLECTION_SYNC_EVENT, FIRESTORE_DOCUMENT_SYNC_EVENT];
}
@end
#else
@implementation RNFirebaseFirestore
@end
#endif

View File

@ -0,0 +1,35 @@
#ifndef RNFirebaseFirestoreCollectionReference_h
#define RNFirebaseFirestoreCollectionReference_h
#import <Foundation/Foundation.h>
#if __has_include(<FirebaseFirestore/FirebaseFirestore.h>)
#import <FirebaseFirestore/FirebaseFirestore.h>
#import <React/RCTEventEmitter.h>
#import "RNFirebaseEvents.h"
#import "RNFirebaseFirestore.h"
#import "RNFirebaseFirestoreDocumentReference.h"
@interface RNFirebaseFirestoreCollectionReference : NSObject
@property RCTEventEmitter *emitter;
@property NSString *app;
@property NSString *path;
@property NSArray *filters;
@property NSArray *orders;
@property NSDictionary *options;
@property FIRQuery *query;
- (id)initWithPathAndModifiers:(RCTEventEmitter *)emitter app:(NSString *)app path:(NSString *)path filters:(NSArray *)filters orders:(NSArray *)orders options:(NSDictionary *)options;
- (void)get:(RCTPromiseResolveBlock) resolve rejecter:(RCTPromiseRejectBlock) reject;
+ (void)offSnapshot:(NSString *)listenerId;
- (void)onSnapshot:(NSString *)listenerId;
+ (NSDictionary *)snapshotToDictionary:(FIRQuerySnapshot *)querySnapshot;
@end
#else
@interface RNFirebaseFirestoreCollectionReference : NSObject
@end
#endif
#endif

View File

@ -0,0 +1,201 @@
#import "RNFirebaseFirestoreCollectionReference.h"
@implementation RNFirebaseFirestoreCollectionReference
#if __has_include(<FirebaseFirestore/FirebaseFirestore.h>)
static NSMutableDictionary *_listeners;
- (id)initWithPathAndModifiers:(RCTEventEmitter *) emitter
app:(NSString *) app
path:(NSString *) path
filters:(NSArray *) filters
orders:(NSArray *) orders
options:(NSDictionary *) options {
self = [super init];
if (self) {
_emitter = emitter;
_app = app;
_path = path;
_filters = filters;
_orders = orders;
_options = options;
_query = [self buildQuery];
}
// Initialise the static listeners object if required
if (!_listeners) {
_listeners = [[NSMutableDictionary alloc] init];
}
return self;
}
- (void)get:(RCTPromiseResolveBlock) resolve
rejecter:(RCTPromiseRejectBlock) reject {
[_query getDocumentsWithCompletion:^(FIRQuerySnapshot * _Nullable snapshot, NSError * _Nullable error) {
if (error) {
[RNFirebaseFirestore promiseRejectException:reject error:error];
} else {
NSDictionary *data = [RNFirebaseFirestoreCollectionReference snapshotToDictionary:snapshot];
resolve(data);
}
}];
}
+ (void)offSnapshot:(NSString *) listenerId {
id<FIRListenerRegistration> listener = _listeners[listenerId];
if (listener) {
[_listeners removeObjectForKey:listenerId];
[listener remove];
}
}
- (void)onSnapshot:(NSString *) listenerId {
if (_listeners[listenerId] == nil) {
id listenerBlock = ^(FIRQuerySnapshot * _Nullable snapshot, NSError * _Nullable error) {
if (error) {
id<FIRListenerRegistration> listener = _listeners[listenerId];
if (listener) {
[_listeners removeObjectForKey:listenerId];
[listener remove];
}
[self handleQuerySnapshotError:listenerId error:error];
} else {
[self handleQuerySnapshotEvent:listenerId querySnapshot:snapshot];
}
};
id<FIRListenerRegistration> listener = [_query addSnapshotListener:listenerBlock];
_listeners[listenerId] = listener;
}
}
- (FIRQuery *)buildQuery {
FIRQuery *query = (FIRQuery*)[[RNFirebaseFirestore getFirestoreForApp:_app] collectionWithPath:_path];
query = [self applyFilters:query];
query = [self applyOrders:query];
query = [self applyOptions:query];
return query;
}
- (FIRQuery *)applyFilters:(FIRQuery *) query {
for (NSDictionary *filter in _filters) {
NSString *fieldPath = filter[@"fieldPath"];
NSString *operator = filter[@"operator"];
// TODO: Validate this works
id value = filter[@"value"];
if ([operator isEqualToString:@"EQUAL"]) {
query = [query queryWhereField:fieldPath isEqualTo:value];
} else if ([operator isEqualToString:@"GREATER_THAN"]) {
query = [query queryWhereField:fieldPath isGreaterThan:value];
} else if ([operator isEqualToString:@"GREATER_THAN_OR_EQUAL"]) {
query = [query queryWhereField:fieldPath isGreaterThanOrEqualTo:value];
} else if ([operator isEqualToString:@"LESS_THAN"]) {
query = [query queryWhereField:fieldPath isLessThan:value];
} else if ([operator isEqualToString:@"LESS_THAN_OR_EQUAL"]) {
query = [query queryWhereField:fieldPath isLessThanOrEqualTo:value];
}
}
return query;
}
- (FIRQuery *)applyOrders:(FIRQuery *) query {
for (NSDictionary *order in _orders) {
NSString *direction = order[@"direction"];
NSString *fieldPath = order[@"fieldPath"];
query = [query queryOrderedByField:fieldPath descending:([direction isEqualToString:@"DESCENDING"])];
}
return query;
}
- (FIRQuery *)applyOptions:(FIRQuery *) query {
if (_options[@"endAt"]) {
query = [query queryEndingAtValues:_options[@"endAt"]];
}
if (_options[@"endBefore"]) {
query = [query queryEndingBeforeValues:_options[@"endBefore"]];
}
if (_options[@"offset"]) {
// iOS doesn't support offset
}
if (_options[@"selectFields"]) {
// iOS doesn't support selectFields
}
if (_options[@"startAfter"]) {
query = [query queryStartingAfterValues:_options[@"startAfter"]];
}
if (_options[@"startAt"]) {
query = [query queryStartingAtValues:_options[@"startAt"]];
}
return query;
}
- (void)handleQuerySnapshotError:(NSString *)listenerId
error:(NSError *)error {
NSMutableDictionary *event = [[NSMutableDictionary alloc] init];
[event setValue:_app forKey:@"appName"];
[event setValue:_path forKey:@"path"];
[event setValue:listenerId forKey:@"listenerId"];
[event setValue:[RNFirebaseFirestore getJSError:error] forKey:@"error"];
[_emitter sendEventWithName:FIRESTORE_COLLECTION_SYNC_EVENT body:event];
}
- (void)handleQuerySnapshotEvent:(NSString *)listenerId
querySnapshot:(FIRQuerySnapshot *)querySnapshot {
NSMutableDictionary *event = [[NSMutableDictionary alloc] init];
[event setValue:_app forKey:@"appName"];
[event setValue:_path forKey:@"path"];
[event setValue:listenerId forKey:@"listenerId"];
[event setValue:[RNFirebaseFirestoreCollectionReference snapshotToDictionary:querySnapshot] forKey:@"querySnapshot"];
[_emitter sendEventWithName:FIRESTORE_COLLECTION_SYNC_EVENT body:event];
}
+ (NSDictionary *)snapshotToDictionary:(FIRQuerySnapshot *)querySnapshot {
NSMutableDictionary *snapshot = [[NSMutableDictionary alloc] init];
[snapshot setValue:[self documentChangesToArray:querySnapshot.documentChanges] forKey:@"changes"];
[snapshot setValue:[self documentSnapshotsToArray:querySnapshot.documents] forKey:@"documents"];
return snapshot;
}
+ (NSArray *)documentChangesToArray:(NSArray<FIRDocumentChange *> *) documentChanges {
NSMutableArray *changes = [[NSMutableArray alloc] init];
for (FIRDocumentChange *change in documentChanges) {
[changes addObject:[self documentChangeToDictionary:change]];
}
return changes;
}
+ (NSDictionary *)documentChangeToDictionary:(FIRDocumentChange *)documentChange {
NSMutableDictionary *change = [[NSMutableDictionary alloc] init];
[change setValue:[RNFirebaseFirestoreDocumentReference snapshotToDictionary:documentChange.document] forKey:@"document"];
[change setValue:@(documentChange.newIndex) forKey:@"newIndex"];
[change setValue:@(documentChange.oldIndex) forKey:@"oldIndex"];
if (documentChange.type == FIRDocumentChangeTypeAdded) {
[change setValue:@"added" forKey:@"type"];
} else if (documentChange.type == FIRDocumentChangeTypeRemoved) {
[change setValue:@"removed" forKey:@"type"];
} else if (documentChange.type == FIRDocumentChangeTypeModified) {
[change setValue:@"modified" forKey:@"type"];
}
return change;
}
+ (NSArray *)documentSnapshotsToArray:(NSArray<FIRDocumentSnapshot *> *) documentSnapshots {
NSMutableArray *snapshots = [[NSMutableArray alloc] init];
for (FIRDocumentSnapshot *snapshot in documentSnapshots) {
[snapshots addObject:[RNFirebaseFirestoreDocumentReference snapshotToDictionary:snapshot]];
}
return snapshots;
}
#endif
@end

View File

@ -0,0 +1,38 @@
#ifndef RNFirebaseFirestoreDocumentReference_h
#define RNFirebaseFirestoreDocumentReference_h
#import <Foundation/Foundation.h>
#if __has_include(<FirebaseFirestore/FirebaseFirestore.h>)
#import <FirebaseFirestore/FirebaseFirestore.h>
#import <React/RCTEventEmitter.h>
#import "RNFirebaseEvents.h"
#import "RNFirebaseFirestore.h"
@interface RNFirebaseFirestoreDocumentReference : NSObject
@property RCTEventEmitter *emitter;
@property NSString *app;
@property NSString *path;
@property FIRDocumentReference *ref;
- (id)initWithPath:(RCTEventEmitter *)emitter app:(NSString *)app path:(NSString *)path;
- (void)collections:(RCTPromiseResolveBlock) resolve rejecter:(RCTPromiseRejectBlock) reject;
- (void)create:(NSDictionary *)data resolver:(RCTPromiseResolveBlock) resolve rejecter:(RCTPromiseRejectBlock) reject;
- (void)delete:(NSDictionary *)options resolver:(RCTPromiseResolveBlock) resolve rejecter:(RCTPromiseRejectBlock) reject;
- (void)get:(RCTPromiseResolveBlock) resolve rejecter:(RCTPromiseRejectBlock) reject;
+ (void)offSnapshot:(NSString *)listenerId;
- (void)onSnapshot:(NSString *)listenerId;
- (void)set:(NSDictionary *)data options:(NSDictionary *)options resolver:(RCTPromiseResolveBlock) resolve rejecter:(RCTPromiseRejectBlock) reject;
- (void)update:(NSDictionary *)data resolver:(RCTPromiseResolveBlock) resolve rejecter:(RCTPromiseRejectBlock) reject;
- (BOOL)hasListeners;
+ (NSDictionary *)snapshotToDictionary:(FIRDocumentSnapshot *)documentSnapshot;
@end
#else
@interface RNFirebaseFirestoreDocumentReference : NSObject
@end
#endif
#endif

View File

@ -0,0 +1,162 @@
#import "RNFirebaseFirestoreDocumentReference.h"
@implementation RNFirebaseFirestoreDocumentReference
#if __has_include(<FirebaseFirestore/FirebaseFirestore.h>)
static NSMutableDictionary *_listeners;
- (id)initWithPath:(RCTEventEmitter *)emitter
app:(NSString *) app
path:(NSString *) path {
self = [super init];
if (self) {
_emitter = emitter;
_app = app;
_path = path;
_ref = [[RNFirebaseFirestore getFirestoreForApp:_app] documentWithPath:_path];
}
// Initialise the static listeners object if required
if (!_listeners) {
_listeners = [[NSMutableDictionary alloc] init];
}
return self;
}
- (void)collections:(RCTPromiseResolveBlock) resolve
rejecter:(RCTPromiseRejectBlock) reject {
// Not supported on iOS
}
- (void)create:(NSDictionary *) data
resolver:(RCTPromiseResolveBlock) resolve
rejecter:(RCTPromiseRejectBlock) reject {
// Not supported on iOS out of the box
}
- (void)delete:(NSDictionary *)options
resolver:(RCTPromiseResolveBlock) resolve
rejecter:(RCTPromiseRejectBlock) reject {
[_ref deleteDocumentWithCompletion:^(NSError * _Nullable error) {
[RNFirebaseFirestoreDocumentReference handleWriteResponse:error resolver:resolve rejecter:reject];
}];
}
- (void)get:(RCTPromiseResolveBlock) resolve
rejecter:(RCTPromiseRejectBlock) reject {
[_ref getDocumentWithCompletion:^(FIRDocumentSnapshot * _Nullable snapshot, NSError * _Nullable error) {
if (error) {
[RNFirebaseFirestore promiseRejectException:reject error:error];
} else {
NSDictionary *data = [RNFirebaseFirestoreDocumentReference snapshotToDictionary:snapshot];
resolve(data);
}
}];
}
+ (void)offSnapshot:(NSString *) listenerId {
id<FIRListenerRegistration> listener = _listeners[listenerId];
if (listener) {
[_listeners removeObjectForKey:listenerId];
[listener remove];
}
}
- (void)onSnapshot:(NSString *) listenerId {
if (_listeners[listenerId] == nil) {
id listenerBlock = ^(FIRDocumentSnapshot * _Nullable snapshot, NSError * _Nullable error) {
if (error) {
id<FIRListenerRegistration> listener = _listeners[listenerId];
if (listener) {
[_listeners removeObjectForKey:listenerId];
[listener remove];
}
[self handleDocumentSnapshotError:listenerId error:error];
} else {
[self handleDocumentSnapshotEvent:listenerId documentSnapshot:snapshot];
}
};
id<FIRListenerRegistration> listener = [_ref addSnapshotListener:listenerBlock];
_listeners[listenerId] = listener;
}
}
- (void)set:(NSDictionary *) data
options:(NSDictionary *) options
resolver:(RCTPromiseResolveBlock) resolve
rejecter:(RCTPromiseRejectBlock) reject {
if (options && options[@"merge"]) {
[_ref setData:data options:[FIRSetOptions merge] completion:^(NSError * _Nullable error) {
[RNFirebaseFirestoreDocumentReference handleWriteResponse:error resolver:resolve rejecter:reject];
}];
} else {
[_ref setData:data completion:^(NSError * _Nullable error) {
[RNFirebaseFirestoreDocumentReference handleWriteResponse:error resolver:resolve rejecter:reject];
}];
}
}
- (void)update:(NSDictionary *) data
resolver:(RCTPromiseResolveBlock) resolve
rejecter:(RCTPromiseRejectBlock) reject {
[_ref updateData:data completion:^(NSError * _Nullable error) {
[RNFirebaseFirestoreDocumentReference handleWriteResponse:error resolver:resolve rejecter:reject];
}];
}
- (BOOL)hasListeners {
return [[_listeners allKeys] count] > 0;
}
+ (void)handleWriteResponse:(NSError *) error
resolver:(RCTPromiseResolveBlock) resolve
rejecter:(RCTPromiseRejectBlock) reject {
if (error) {
[RNFirebaseFirestore promiseRejectException:reject error:error];
} else {
// Missing fields from web SDK
// writeTime
resolve(@{});
}
}
+ (NSDictionary *)snapshotToDictionary:(FIRDocumentSnapshot *)documentSnapshot {
NSMutableDictionary *snapshot = [[NSMutableDictionary alloc] init];
[snapshot setValue:documentSnapshot.reference.path forKey:@"path"];
if (documentSnapshot.exists) {
[snapshot setValue:documentSnapshot.data forKey:@"data"];
}
// Missing fields from web SDK
// createTime
// readTime
// updateTime
return snapshot;
}
- (void)handleDocumentSnapshotError:(NSString *)listenerId
error:(NSError *)error {
NSMutableDictionary *event = [[NSMutableDictionary alloc] init];
[event setValue:_app forKey:@"appName"];
[event setValue:_path forKey:@"path"];
[event setValue:listenerId forKey:@"listenerId"];
[event setValue:[RNFirebaseFirestore getJSError:error] forKey:@"error"];
[_emitter sendEventWithName:FIRESTORE_DOCUMENT_SYNC_EVENT body:event];
}
- (void)handleDocumentSnapshotEvent:(NSString *)listenerId
documentSnapshot:(FIRDocumentSnapshot *)documentSnapshot {
NSMutableDictionary *event = [[NSMutableDictionary alloc] init];
[event setValue:_app forKey:@"appName"];
[event setValue:_path forKey:@"path"];
[event setValue:listenerId forKey:@"listenerId"];
[event setValue:[RNFirebaseFirestoreDocumentReference snapshotToDictionary:documentSnapshot] forKey:@"documentSnapshot"];
[_emitter sendEventWithName:FIRESTORE_DOCUMENT_SYNC_EVENT body:event];
}
#endif
@end

View File

@ -12,6 +12,7 @@ import RemoteConfig from './modules/config';
import Storage, { statics as StorageStatics } from './modules/storage';
import Database, { statics as DatabaseStatics } from './modules/database';
import Messaging, { statics as MessagingStatics } from './modules/messaging';
import Firestore, { statics as FirestoreStatics } from './modules/firestore';
const FirebaseCoreModule = NativeModules.RNFirebase;
@ -32,6 +33,7 @@ export default class FirebaseApp {
this.config = this._staticsOrModuleInstance({}, RemoteConfig);
this.crash = this._staticsOrModuleInstance({}, Crash);
this.database = this._staticsOrModuleInstance(DatabaseStatics, Database);
this.firestore = this._staticsOrModuleInstance(FirestoreStatics, Firestore);
this.messaging = this._staticsOrModuleInstance(MessagingStatics, Messaging);
this.perf = this._staticsOrModuleInstance({}, Performance);
this.storage = this._staticsOrModuleInstance(StorageStatics, Storage);

View File

@ -20,6 +20,7 @@ import RemoteConfig from './modules/config';
import Storage, { statics as StorageStatics } from './modules/storage';
import Database, { statics as DatabaseStatics } from './modules/database';
import Messaging, { statics as MessagingStatics } from './modules/messaging';
import Firestore, { statics as FirestoreStatics } from './modules/firestore';
const FirebaseCoreModule = NativeModules.RNFirebase;
@ -47,6 +48,7 @@ class FirebaseCore {
this.config = this._appNamespaceOrStatics({}, RemoteConfig);
this.crash = this._appNamespaceOrStatics({}, Crash);
this.database = this._appNamespaceOrStatics(DatabaseStatics, Database);
this.firestore = this._appNamespaceOrStatics(FirestoreStatics, Firestore);
this.messaging = this._appNamespaceOrStatics(MessagingStatics, Messaging);
this.perf = this._appNamespaceOrStatics(DatabaseStatics, Performance);
this.storage = this._appNamespaceOrStatics(StorageStatics, Storage);

View File

@ -0,0 +1,105 @@
/**
* @flow
* CollectionReference representation wrapper
*/
import DocumentReference from './DocumentReference';
import Path from './Path';
import Query from './Query';
import QuerySnapshot from './QuerySnapshot';
import { firestoreAutoId } from '../../utils';
import type { Direction, Operator } from './Query';
/**
* @class CollectionReference
*/
export default class CollectionReference {
_collectionPath: Path;
_firestore: Object;
_query: Query;
constructor(firestore: Object, collectionPath: Path) {
this._collectionPath = collectionPath;
this._firestore = firestore;
this._query = new Query(firestore, collectionPath);
}
get firestore(): Object {
return this._firestore;
}
get id(): string | null {
return this._collectionPath.id;
}
get parent(): DocumentReference | null {
const parentPath = this._collectionPath.parent();
return parentPath ? new DocumentReference(this._firestore, parentPath) : null;
}
add(data: { [string]: any }): Promise<DocumentReference> {
const documentRef = this.doc();
return documentRef.set(data)
.then(() => Promise.resolve(documentRef));
}
doc(documentPath?: string): DocumentReference {
const newPath = documentPath || firestoreAutoId();
const path = this._collectionPath.child(newPath);
if (!path.isDocument) {
throw new Error('Argument "documentPath" must point to a document.');
}
return new DocumentReference(this._firestore, path);
}
// From Query
endAt(fieldValues: any): Query {
return this._query.endAt(fieldValues);
}
endBefore(fieldValues: any): Query {
return this._query.endBefore(fieldValues);
}
get(): Promise<QuerySnapshot> {
return this._query.get();
}
limit(n: number): Query {
return this._query.limit(n);
}
offset(n: number): Query {
return this._query.offset(n);
}
onSnapshot(onNext: () => any, onError?: () => any): () => void {
return this._query.onSnapshot(onNext, onError);
}
orderBy(fieldPath: string, directionStr?: Direction): Query {
return this._query.orderBy(fieldPath, directionStr);
}
select(varArgs: string[]): Query {
return this._query.select(varArgs);
}
startAfter(fieldValues: any): Query {
return this._query.startAfter(fieldValues);
}
startAt(fieldValues: any): Query {
return this._query.startAt(fieldValues);
}
stream(): Stream<DocumentSnapshot> {
return this._query.stream();
}
where(fieldPath: string, opStr: Operator, value: any): Query {
return this._query.where(fieldPath, opStr, value);
}
}

View File

@ -0,0 +1,46 @@
/**
* @flow
* DocumentChange representation wrapper
*/
import DocumentSnapshot from './DocumentSnapshot';
export type DocumentChangeNativeData = {
document: DocumentSnapshot,
newIndex: number,
oldIndex: number,
type: string,
}
/**
* @class DocumentChange
*/
export default class DocumentChange {
_document: DocumentSnapshot;
_newIndex: number;
_oldIndex: number;
_type: string;
constructor(nativeData: DocumentChangeNativeData) {
this._document = nativeData.document;
this._newIndex = nativeData.newIndex;
this._oldIndex = nativeData.oldIndex;
this._type = nativeData.type;
}
get doc(): DocumentSnapshot {
return this._document;
}
get newIndex(): number {
return this._newIndex;
}
get oldIndex(): number {
return this._oldIndex;
}
get type(): string {
return this._type;
}
}

View File

@ -0,0 +1,146 @@
/**
* @flow
* DocumentReference representation wrapper
*/
import CollectionReference from './CollectionReference';
import DocumentSnapshot from './DocumentSnapshot';
import Path from './Path';
import INTERNALS from './../../internals';
import { firestoreAutoId } from '../../utils';
export type DeleteOptions = {
lastUpdateTime?: string,
}
export type WriteOptions = {
merge?: boolean,
}
export type WriteResult = {
writeTime: string,
}
/**
* @class DocumentReference
*/
export default class DocumentReference {
_documentPath: Path;
_firestore: Object;
constructor(firestore: Object, documentPath: Path) {
this._documentPath = documentPath;
this._firestore = firestore;
}
get firestore(): Object {
return this._firestore;
}
get id(): string | null {
return this._documentPath.id;
}
get parent(): CollectionReference | null {
const parentPath = this._documentPath.parent();
return parentPath ? new CollectionReference(this._firestore, parentPath) : null;
}
get path(): string {
return this._documentPath.relativeName;
}
collection(collectionPath: string): CollectionReference {
const path = this._documentPath.child(collectionPath);
if (!path.isCollection) {
throw new Error('Argument "collectionPath" must point to a collection.');
}
return new CollectionReference(this._firestore, path);
}
create(data: { [string]: any }): Promise<WriteResult> {
/* return this._firestore._native
.documentCreate(this.path, data); */
throw new Error(INTERNALS.STRINGS.ERROR_UNSUPPORTED_CLASS_METHOD('DocumentReference', 'create'));
}
delete(deleteOptions?: DeleteOptions): Promise<WriteResult> {
return this._firestore._native
.documentDelete(this.path, deleteOptions);
}
get(): Promise<DocumentSnapshot> {
return this._firestore._native
.documentGet(this.path)
.then(result => new DocumentSnapshot(this._firestore, result));
}
getCollections(): Promise<CollectionReference[]> {
/* return this._firestore._native
.documentCollections(this.path)
.then((collectionIds) => {
const collections = [];
for (const collectionId of collectionIds) {
collections.push(this.collection(collectionId));
}
return collections;
}); */
throw new Error(INTERNALS.STRINGS.ERROR_UNSUPPORTED_CLASS_METHOD('DocumentReference', 'getCollections'));
}
onSnapshot(onNext: Function, onError?: Function): () => void {
// TODO: Validation
const listenerId = firestoreAutoId();
const listener = (nativeDocumentSnapshot) => {
const documentSnapshot = new DocumentSnapshot(this, nativeDocumentSnapshot);
onNext(documentSnapshot);
};
// Listen to snapshot events
this._firestore.on(
this._firestore._getAppEventName(`onDocumentSnapshot:${listenerId}`),
listener,
);
// Listen for snapshot error events
if (onError) {
this._firestore.on(
this._firestore._getAppEventName(`onDocumentSnapshotError:${listenerId}`),
onError,
);
}
// Add the native listener
this._firestore._native
.documentOnSnapshot(this.path, listenerId);
// Return an unsubscribe method
return this._offDocumentSnapshot.bind(this, listenerId, listener);
}
set(data: { [string]: any }, writeOptions?: WriteOptions): Promise<WriteResult> {
return this._firestore._native
.documentSet(this.path, data, writeOptions);
}
// TODO: Update to new update method signature
update(data: { [string]: any }): Promise<WriteResult> {
return this._firestore._native
.documentUpdate(this.path, data);
}
/**
* Remove document snapshot listener
* @param listener
*/
_offDocumentSnapshot(listenerId: number, listener: Function) {
this._firestore.log.info('Removing onDocumentSnapshot listener');
this._firestore.removeListener(this._firestore._getAppEventName(`onDocumentSnapshot:${listenerId}`), listener);
this._firestore.removeListener(this._firestore._getAppEventName(`onDocumentSnapshotError:${listenerId}`), listener);
this._firestore._native
.documentOffSnapshot(this.path, listenerId);
}
}

View File

@ -0,0 +1,69 @@
/**
* @flow
* DocumentSnapshot representation wrapper
*/
import DocumentReference from './DocumentReference';
import Path from './Path';
import INTERNALS from './../../internals';
export type DocumentSnapshotNativeData = {
createTime: string,
data: Object,
path: string,
readTime: string,
updateTime: string,
}
/**
* @class DocumentSnapshot
*/
export default class DocumentSnapshot {
_createTime: string;
_data: Object;
_readTime: string;
_ref: DocumentReference;
_updateTime: string;
constructor(firestore: Object, nativeData: DocumentSnapshotNativeData) {
this._createTime = nativeData.createTime;
this._data = nativeData.data;
this._ref = new DocumentReference(firestore, Path.fromName(nativeData.path));
this._readTime = nativeData.readTime;
this._updateTime = nativeData.updateTime;
}
get createTime(): string {
// return this._createTime;
throw new Error(INTERNALS.STRINGS.ERROR_UNSUPPORTED_CLASS_PROPERTY('DocumentSnapshot', 'createTime'));
}
get exists(): boolean {
return this._data !== undefined;
}
get id(): string | null {
return this._ref.id;
}
get readTime(): string {
// return this._readTime;
throw new Error(INTERNALS.STRINGS.ERROR_UNSUPPORTED_CLASS_PROPERTY('DocumentSnapshot', 'readTime'));
}
get ref(): DocumentReference {
return this._ref;
}
get updateTime(): string {
// return this._updateTime;
throw new Error(INTERNALS.STRINGS.ERROR_UNSUPPORTED_CLASS_PROPERTY('DocumentSnapshot', 'updateTime'));
}
data(): Object {
return this._data;
}
get(fieldPath: string): any {
return this._data[fieldPath];
}
}

View File

@ -0,0 +1,29 @@
/**
* @flow
* GeoPoint representation wrapper
*/
/**
* @class GeoPoint
*/
export default class GeoPoint {
_latitude: number;
_longitude: number;
constructor(latitude: number, longitude: number) {
// TODO: Validation
// validate.isNumber('latitude', latitude);
// validate.isNumber('longitude', longitude);
this._latitude = latitude;
this._longitude = longitude;
}
get latitude() {
return this._latitude;
}
get longitude() {
return this._longitude;
}
}

View File

@ -0,0 +1,59 @@
/**
* @flow
* Path representation wrapper
*/
/**
* @class Path
*/
export default class Path {
_parts: string[];
constructor(pathComponents: string[]) {
this._parts = pathComponents;
}
get id(): string | null {
if (this._parts.length > 0) {
return this._parts[this._parts.length - 1];
}
return null;
}
get isDocument(): boolean {
return this._parts.length > 0 && this._parts.length % 2 === 0;
}
get isCollection(): boolean {
return this._parts.length % 2 === 1;
}
get relativeName(): string {
return this._parts.join('/');
}
child(relativePath: string): Path {
return new Path(this._parts.concat(relativePath.split('/')));
}
parent(): Path | null {
if (this._parts.length === 0) {
return null;
}
return new Path(this._parts.slice(0, this._parts.length - 1));
}
/**
*
* @package
*/
static fromName(name): Path {
const parts = name.split('/');
if (parts.length === 0) {
return new Path([]);
}
return new Path(parts);
}
}

View File

@ -0,0 +1,272 @@
/**
* @flow
* Query representation wrapper
*/
import DocumentSnapshot from './DocumentSnapshot';
import Path from './Path';
import QuerySnapshot from './QuerySnapshot';
import INTERNALS from '../../internals';
import { firestoreAutoId } from '../../utils';
const DIRECTIONS = {
ASC: 'ASCENDING',
asc: 'ASCENDING',
DESC: 'DESCENDING',
desc: 'DESCENDING',
};
const OPERATORS = {
'=': 'EQUAL',
'==': 'EQUAL',
'>': 'GREATER_THAN',
'>=': 'GREATER_THAN_OR_EQUAL',
'<': 'LESS_THAN',
'<=': 'LESS_THAN_OR_EQUAL',
};
export type Direction = 'DESC' | 'desc' | 'ASC' | 'asc';
type FieldFilter = {
fieldPath: string,
operator: string,
value: any,
}
type FieldOrder = {
direction: string,
fieldPath: string,
}
type QueryOptions = {
endAt?: any[],
endBefore?: any[],
limit?: number,
offset?: number,
selectFields?: string[],
startAfter?: any[],
startAt?: any[],
}
export type Operator = '<' | '<=' | '=' | '==' | '>' | '>=';
/**
* @class Query
*/
export default class Query {
_fieldFilters: FieldFilter[];
_fieldOrders: FieldOrder[];
_firestore: Object;
_iid: number;
_queryOptions: QueryOptions;
_referencePath: Path;
constructor(firestore: Object, path: Path, fieldFilters?: FieldFilter[],
fieldOrders?: FieldOrder[], queryOptions?: QueryOptions) {
this._fieldFilters = fieldFilters || [];
this._fieldOrders = fieldOrders || [];
this._firestore = firestore;
this._queryOptions = queryOptions || {};
this._referencePath = path;
}
get firestore(): Object {
return this._firestore;
}
endAt(fieldValues: any): Query {
fieldValues = [].slice.call(arguments);
// TODO: Validation
const options = {
...this._queryOptions,
endAt: fieldValues,
};
return new Query(this.firestore, this._referencePath, this._fieldFilters,
this._fieldOrders, options);
}
endBefore(fieldValues: any): Query {
fieldValues = [].slice.call(arguments);
// TODO: Validation
const options = {
...this._queryOptions,
endBefore: fieldValues,
};
return new Query(this.firestore, this._referencePath, this._fieldFilters,
this._fieldOrders, options);
}
get(): Promise<QuerySnapshot> {
return this._firestore._native
.collectionGet(
this._referencePath.relativeName,
this._fieldFilters,
this._fieldOrders,
this._queryOptions,
)
.then(nativeData => new QuerySnapshot(this._firestore, this, nativeData));
}
limit(n: number): Query {
// TODO: Validation
// validate.isInteger('n', n);
const options = {
...this._queryOptions,
limit: n,
};
return new Query(this.firestore, this._referencePath, this._fieldFilters,
this._fieldOrders, options);
}
offset(n: number): Query {
// TODO: Validation
// validate.isInteger('n', n);
/* const options = {
...this._queryOptions,
offset: n,
};
return new Query(this.firestore, this._referencePath, this._fieldFilters,
this._fieldOrders, options); */
throw new Error(INTERNALS.STRINGS.ERROR_UNSUPPORTED_CLASS_METHOD('Query', 'offset'));
}
onSnapshot(onNext: () => any, onError?: () => any): () => void {
// TODO: Validation
const listenerId = firestoreAutoId();
const listener = (nativeQuerySnapshot) => {
const querySnapshot = new QuerySnapshot(this._firestore, this, nativeQuerySnapshot);
onNext(querySnapshot);
};
// Listen to snapshot events
this._firestore.on(
this._firestore._getAppEventName(`onQuerySnapshot:${listenerId}`),
listener,
);
// Listen for snapshot error events
if (onError) {
this._firestore.on(
this._firestore._getAppEventName(`onQuerySnapshotError:${listenerId}`),
onError,
);
}
// Add the native listener
this._firestore._native
.collectionOnSnapshot(
this._referencePath.relativeName,
this._fieldFilters,
this._fieldOrders,
this._queryOptions,
listenerId
);
// Return an unsubscribe method
return this._offCollectionSnapshot.bind(this, listenerId, listener);
}
orderBy(fieldPath: string, directionStr?: Direction = 'asc'): Query {
// TODO: Validation
// validate.isFieldPath('fieldPath', fieldPath);
// validate.isOptionalFieldOrder('directionStr', directionStr);
if (this._queryOptions.startAt || this._queryOptions.endAt) {
throw new Error('Cannot specify an orderBy() constraint after calling ' +
'startAt(), startAfter(), endBefore() or endAt().');
}
const newOrder = {
direction: DIRECTIONS[directionStr],
fieldPath,
};
const combinedOrders = this._fieldOrders.concat(newOrder);
return new Query(this.firestore, this._referencePath, this._fieldFilters,
combinedOrders, this._queryOptions);
}
select(varArgs: string[]): Query {
/*
varArgs = Array.isArray(arguments[0]) ? arguments[0] : [].slice.call(arguments);
const fieldReferences = [];
if (varArgs.length === 0) {
fieldReferences.push(DOCUMENT_NAME_FIELD);
} else {
for (let i = 0; i < varArgs.length; ++i) {
// TODO: Validation
// validate.isFieldPath(i, args[i]);
fieldReferences.push(varArgs[i]);
}
}
const options = {
...this._queryOptions,
selectFields: fieldReferences,
};
return new Query(this.firestore, this._referencePath, this._fieldFilters,
this._fieldOrders, options);*/
throw new Error(INTERNALS.STRINGS.ERROR_UNSUPPORTED_CLASS_METHOD('Query', 'select'));
}
startAfter(fieldValues: any): Query {
fieldValues = [].slice.call(arguments);
// TODO: Validation
const options = {
...this._queryOptions,
startAfter: fieldValues,
};
return new Query(this.firestore, this._referencePath, this._fieldFilters,
this._fieldOrders, options);
}
startAt(fieldValues: any): Query {
fieldValues = [].slice.call(arguments);
// TODO: Validation
const options = {
...this._queryOptions,
startAt: fieldValues,
};
return new Query(this.firestore, this._referencePath, this._fieldFilters,
this._fieldOrders, options);
}
stream(): Stream<DocumentSnapshot> {
throw new Error(INTERNALS.STRINGS.ERROR_UNSUPPORTED_CLASS_METHOD('Query', 'stream'));
}
where(fieldPath: string, opStr: Operator, value: any): Query {
// TODO: Validation
// validate.isFieldPath('fieldPath', fieldPath);
// validate.isFieldFilter('fieldFilter', opStr, value);
const newFilter = {
fieldPath,
operator: OPERATORS[opStr],
value,
};
const combinedFilters = this._fieldFilters.concat(newFilter);
return new Query(this.firestore, this._referencePath, combinedFilters,
this._fieldOrders, this._queryOptions);
}
/**
* Remove query snapshot listener
* @param listener
*/
_offCollectionSnapshot(listenerId: number, listener: Function) {
this._firestore.log.info('Removing onQuerySnapshot listener');
this._firestore.removeListener(this._firestore._getAppEventName(`onQuerySnapshot:${listenerId}`), listener);
this._firestore.removeListener(this._firestore._getAppEventName(`onQuerySnapshotError:${listenerId}`), listener);
this._firestore._native
.collectionOffSnapshot(
this._referencePath.relativeName,
this._fieldFilters,
this._fieldOrders,
this._queryOptions,
listenerId
);
}
}

View File

@ -0,0 +1,66 @@
/**
* @flow
* QuerySnapshot representation wrapper
*/
import DocumentChange from './DocumentChange';
import DocumentSnapshot from './DocumentSnapshot';
import Query from './Query';
import type { DocumentChangeNativeData } from './DocumentChange';
import type { DocumentSnapshotNativeData } from './DocumentSnapshot';
type QuerySnapshotNativeData = {
changes: DocumentChangeNativeData[],
documents: DocumentSnapshotNativeData[],
readTime: string,
}
/**
* @class QuerySnapshot
*/
export default class QuerySnapshot {
_changes: DocumentChange[];
_docs: DocumentSnapshot[];
_query: Query;
_readTime: string;
constructor(firestore: Object, query: Query, nativeData: QuerySnapshotNativeData) {
this._changes = nativeData.changes.map(change => new DocumentChange(change));
this._docs = nativeData.documents.map(doc => new DocumentSnapshot(firestore, doc));
this._query = query;
this._readTime = nativeData.readTime;
}
get docChanges(): DocumentChange[] {
return this._changes;
}
get docs(): DocumentSnapshot[] {
return this._docs;
}
get empty(): boolean {
return this._docs.length === 0;
}
get query(): Query {
return this._query;
}
get readTime(): string {
return this._readTime;
}
get size(): number {
return this._docs.length;
}
forEach(callback: DocumentSnapshot => any) {
// TODO: Validation
// validate.isFunction('callback', callback);
for (const doc of this._docs) {
callback(doc);
}
}
}

View File

@ -0,0 +1,96 @@
/**
* @flow
* WriteBatch representation wrapper
*/
import DocumentReference from './DocumentReference';
import type { DeleteOptions, WriteOptions, WriteResult } from './DocumentReference';
type CommitOptions = {
transactionId: string,
}
type DocumentWrite = {
data?: Object,
options?: Object,
path: string,
type: 'DELETE' | 'SET' | 'UPDATE',
}
/**
* @class WriteBatch
*/
export default class WriteBatch {
_firestore: Object;
_writes: DocumentWrite[];
constructor(firestore: Object) {
this._firestore = firestore;
this._writes = [];
}
get firestore(): Object {
return this._firestore;
}
get isEmpty(): boolean {
return this._writes.length === 0;
}
create(docRef: DocumentReference, data: Object): WriteBatch {
// TODO: Validation
// validate.isDocumentReference('docRef', docRef);
// validate.isDocument('data', data);
return this.set(docRef, data, { exists: false });
}
delete(docRef: DocumentReference, deleteOptions?: DeleteOptions): WriteBatch {
// TODO: Validation
// validate.isDocumentReference('docRef', docRef);
// validate.isOptionalPrecondition('deleteOptions', deleteOptions);
this._writes.push({
options: deleteOptions,
path: docRef.path,
type: 'DELETE',
});
return this;
}
set(docRef: DocumentReference, data: Object, writeOptions?: WriteOptions) {
// TODO: Validation
// validate.isDocumentReference('docRef', docRef);
// validate.isDocument('data', data);
// validate.isOptionalPrecondition('writeOptions', writeOptions);
this._writes.push({
data,
options: writeOptions,
path: docRef.path,
type: 'SET',
});
return this;
}
// TODO: Update to new method signature
update(docRef: DocumentReference, data: { [string]: any }): WriteBatch {
// TODO: Validation
// validate.isDocumentReference('docRef', docRef);
// validate.isDocument('data', data, true);
this._writes.push({
data,
path: docRef.path,
type: 'UPDATE',
});
return this;
}
commit(commitOptions?: CommitOptions): Promise<WriteResult[]> {
return this._firestore._native
.documentBatch(this._writes, commitOptions);
}
}

View File

@ -0,0 +1,177 @@
/**
* @flow
* Firestore representation wrapper
*/
import { NativeModules } from 'react-native';
import ModuleBase from './../../utils/ModuleBase';
import CollectionReference from './CollectionReference';
import DocumentReference from './DocumentReference';
import DocumentSnapshot from './DocumentSnapshot';
import GeoPoint from './GeoPoint';
import Path from './Path';
import WriteBatch from './WriteBatch';
import INTERNALS from './../../internals';
const unquotedIdentifier_ = '(?:[A-Za-z_][A-Za-z_0-9]*)';
const UNQUOTED_IDENTIFIER_REGEX = new RegExp(`^${unquotedIdentifier_}$`);
type CollectionSyncEvent = {
appName: string,
querySnapshot?: QuerySnapshot,
error?: Object,
listenerId: string,
path: string,
}
type DocumentSyncEvent = {
appName: string,
documentSnapshot?: DocumentSnapshot,
error?: Object,
listenerId: string,
path: string,
}
/**
* @class Firestore
*/
export default class Firestore extends ModuleBase {
static _NAMESPACE = 'firestore';
static _NATIVE_MODULE = 'RNFirebaseFirestore';
_referencePath: Path;
constructor(firebaseApp: Object, options: Object = {}) {
super(firebaseApp, options, true);
this._referencePath = new Path([]);
this.addListener(
// sub to internal native event - this fans out to
// public event name: onCollectionSnapshot
this._getAppEventName('firestore_collection_sync_event'),
this._onCollectionSyncEvent.bind(this),
);
this.addListener(
// sub to internal native event - this fans out to
// public event name: onDocumentSnapshot
this._getAppEventName('firestore_document_sync_event'),
this._onDocumentSyncEvent.bind(this),
);
}
batch(): WriteBatch {
return new WriteBatch(this);
}
/**
*
* @param collectionPath
* @returns {CollectionReference}
*/
collection(collectionPath: string): CollectionReference {
const path = this._referencePath.child(collectionPath);
if (!path.isCollection) {
throw new Error('Argument "collectionPath" must point to a collection.');
}
return new CollectionReference(this, path);
}
/**
*
* @param documentPath
* @returns {DocumentReference}
*/
doc(documentPath: string): DocumentReference {
const path = this._referencePath.child(documentPath);
if (!path.isDocument) {
throw new Error('Argument "documentPath" must point to a document.');
}
return new DocumentReference(this, path);
}
getAll(varArgs: DocumentReference[]): Promise<DocumentSnapshot[]> {
/*varArgs = Array.isArray(arguments[0]) ? arguments[0] : [].slice.call(arguments);
const documents = [];
varArgs.forEach((document) => {
// TODO: Validation
// validate.isDocumentReference(i, varArgs[i]);
documents.push(document.path);
});
return this._native
.documentGetAll(documents)
.then(results => results.map(result => new DocumentSnapshot(this, result)));*/
throw new Error(INTERNALS.STRINGS.ERROR_UNSUPPORTED_MODULE_METHOD('Query', 'offset'));
}
getCollections(): Promise<CollectionReference[]> {
const rootDocument = new DocumentReference(this, this._referencePath);
return rootDocument.getCollections();
}
runTransaction(updateFunction, transactionOptions?: Object): Promise {
}
static geoPoint(latitude, longitude): GeoPoint {
return new GeoPoint(latitude, longitude);
}
static fieldPath(varArgs: string[]): string {
varArgs = Array.isArray(arguments[0]) ? arguments[0] : [].slice.call(arguments);
let fieldPath = '';
for (let i = 0; i < varArgs.length; ++i) {
let component = varArgs[i];
// TODO: Validation
// validate.isString(i, component);
if (!UNQUOTED_IDENTIFIER_REGEX.test(component)) {
component = `\`${component.replace(/[`\\]/g, '\\$&')} \``;
}
fieldPath += i !== 0 ? `.${component}` : component;
}
return fieldPath;
}
/**
* INTERNALS
*/
/**
* Internal collection sync listener
* @param event
* @private
*/
_onCollectionSyncEvent(event: CollectionSyncEvent) {
if (event.error) {
this.emit(this._getAppEventName(`onQuerySnapshotError:${event.listenerId}`), event.error);
} else {
this.emit(this._getAppEventName(`onQuerySnapshot:${event.listenerId}`), event.querySnapshot);
}
}
/**
* Internal document sync listener
* @param event
* @private
*/
_onDocumentSyncEvent(event: DocumentSyncEvent) {
if (event.error) {
this.emit(this._getAppEventName(`onDocumentSnapshotError:${event.listenerId}`), event.error);
} else {
this.emit(this._getAppEventName(`onDocumentSnapshot:${event.listenerId}`), event.documentSnapshot);
}
}
}
export const statics = {
FieldValue: {
delete: () => NativeModules.RNFirebaseFirestore && NativeModules.RNFirebaseFirestore.deleteFieldValue || {},
serverTimestamp: () => NativeModules.RNFirebaseFirestore && NativeModules.RNFirebaseFirestore.serverTimestampFieldValue || {}
},
};

View File

@ -15,6 +15,7 @@ const logs = {};
const MULTI_APP_MODULES = [
'auth',
'database',
'firestore',
'storage',
];
@ -31,6 +32,10 @@ const NATIVE_MODULE_EVENTS = {
'database_transaction_event',
// 'database_server_offset', // TODO
],
Firestore: [
'firestore_collection_sync_event',
'firestore_document_sync_event',
],
};
const DEFAULTS = {

View File

@ -5,10 +5,19 @@ import { Platform } from 'react-native';
// modeled after base64 web-safe chars, but ordered by ASCII
const PUSH_CHARS = '-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz';
const AUTO_ID_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
const hasOwnProperty = Object.hasOwnProperty;
// const DEFAULT_CHUNK_SIZE = 50;
// Source: https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical
const REGEXP_FIELD_NAME = new RegExp(
`^(?:\\.?((?:(?:[A-Za-z_][A-Za-z_0-9]*)|(?:[A-Za-z_][A-Za-z_0-9]*))+))$`
);
const REGEXP_FIELD_PATH = new RegExp(
`^((?:(?:[A-Za-z_][A-Za-z_0-9]*)|(?:[A-Za-z_][A-Za-z_0-9]*))+)(?:\\.((?:(?:[A-Za-z_][A-Za-z_0-9]*)|(?:[A-Za-z_][A-Za-z_0-9]*))+))*$`
);
/**
* Deep get a value from an object.
* @website https://github.com/Salakar/deeps
@ -88,6 +97,16 @@ export function isString(value: any): boolean {
return typeof value === 'string';
}
/**
* Firestore field name/path validator.
* @param field
* @param paths
* @return {boolean}
*/
export function isValidFirestoreField(field, paths) {
return (paths ? REGEXP_FIELD_PATH : REGEXP_FIELD_NAME).test(field);
}
// platform checks
export const isIOS = Platform.OS === 'ios';
export const isAndroid = Platform.OS === 'android';
@ -374,3 +393,16 @@ export function promiseOrCallback(promise: Promise<*>, optionalCallback?: Functi
return Promise.reject(error);
});
}
/**
* Generate a firestore auto id for use with collection/document .add()
* @return {string}
*/
export function firestoreAutoId(): string {
let autoId = '';
for (let i = 0; i < 20; i++) {
autoId += AUTO_ID_CHARS.charAt(Math.floor(Math.random() * AUTO_ID_CHARS.length));
}
return autoId;
}

6
package-lock.json generated
View File

@ -2624,9 +2624,9 @@
}
},
"flow-bin": {
"version": "0.46.0",
"resolved": "https://registry.npmjs.org/flow-bin/-/flow-bin-0.46.0.tgz",
"integrity": "sha1-Bq1/4Z3dsQQiZEOAZKKjL+4SuHI=",
"version": "0.55.0",
"resolved": "https://registry.npmjs.org/flow-bin/-/flow-bin-0.55.0.tgz",
"integrity": "sha1-kIPakye9jKtrQHbWPYXyJHp+rhs=",
"dev": true
},
"for-in": {

View File

@ -2,7 +2,7 @@
"name": "react-native-firebase",
"version": "3.0.0-alpha.5",
"author": "Invertase <contact@invertase.io> (http://invertase.io)",
"description": "A well tested, feature rich Firebase implementation for React Native, supporting iOS & Android. Individual module support for Auth, Database, Messaging (FCM), Remote Config, Storage, Admob, Analytics, Crash Reporting, and Performance.",
"description": "A well tested, feature rich Firebase implementation for React Native, supporting iOS & Android. Individual module support for Auth, Database, Firestore, Messaging (FCM), Remote Config, Storage, Admob, Analytics, Crash Reporting, and Performance.",
"main": "index",
"scripts": {
"flow": "flow",
@ -51,7 +51,8 @@
"ios",
"crash",
"firestack",
"performance"
"performance",
"firestore"
],
"peerDependencies": {
"react": "*",
@ -70,7 +71,7 @@
"eslint-plugin-import": "^2.0.1",
"eslint-plugin-jsx-a11y": "^2.2.3",
"eslint-plugin-react": "^6.4.1",
"flow-bin": "^0.46.0",
"flow-bin": "^0.55.0",
"react": "^15.3.0",
"react-dom": "^15.3.0",
"react-native": "^0.44.0",

View File

@ -71,7 +71,7 @@ android {
}
}
project.ext.firebaseVersion = '11.2.0'
project.ext.firebaseVersion = '11.4.2'
dependencies {
// compile(project(':react-native-firebase')) {
@ -82,7 +82,6 @@ dependencies {
compile fileTree(dir: "libs", include: ["*.jar"])
compile "com.google.android.gms:play-services-base:$firebaseVersion"
compile "com.google.firebase:firebase-ads:$firebaseVersion"
compile "com.google.firebase:firebase-ads:$firebaseVersion"
compile "com.google.firebase:firebase-auth:$firebaseVersion"
compile "com.google.firebase:firebase-config:$firebaseVersion"
compile "com.google.firebase:firebase-core:$firebaseVersion"
@ -91,6 +90,7 @@ dependencies {
compile "com.google.firebase:firebase-messaging:$firebaseVersion"
compile "com.google.firebase:firebase-perf:$firebaseVersion"
compile "com.google.firebase:firebase-storage:$firebaseVersion"
compile "com.google.firebase:firebase-firestore:$firebaseVersion"
compile "com.android.support:appcompat-v7:26.0.1"
compile "com.facebook.react:react-native:+" // From node_modules
}

Binary file not shown.

View File

@ -10,8 +10,8 @@ import io.invertase.firebase.auth.RNFirebaseAuthPackage;
import io.invertase.firebase.config.RNFirebaseRemoteConfigPackage;
import io.invertase.firebase.crash.RNFirebaseCrashPackage;
import io.invertase.firebase.database.RNFirebaseDatabasePackage;
import io.invertase.firebase.firestore.RNFirebaseFirestorePackage;
import io.invertase.firebase.messaging.RNFirebaseMessagingPackage;
import io.invertase.firebase.perf.RNFirebasePerformancePackage;
import io.invertase.firebase.storage.RNFirebaseStoragePackage;
import com.oblador.vectoricons.VectorIconsPackage;
import com.facebook.react.ReactNativeHost;
@ -42,8 +42,9 @@ public class MainApplication extends Application implements ReactApplication {
new RNFirebaseRemoteConfigPackage(),
new RNFirebaseCrashPackage(),
new RNFirebaseDatabasePackage(),
new RNFirebaseFirestorePackage(),
new RNFirebaseMessagingPackage(),
new RNFirebasePerformancePackage(),
// new RNFirebasePerformancePackage(),
new RNFirebaseStoragePackage()
);
}

View File

@ -6,8 +6,8 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.3'
classpath 'com.google.gms:google-services:3.1.0'
classpath 'com.google.firebase:firebase-plugins:1.1.0'
classpath 'com.google.gms:google-services:3.1.1'
classpath 'com.google.firebase:firebase-plugins:1.1.1'
}
}
@ -18,6 +18,7 @@ allprojects {
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
url "$rootDir/../node_modules/react-native/android"
}
mavenLocal()
google()
}
}

View File

@ -37,4 +37,4 @@
<key>DATABASE_URL</key>
<string>https://rnfirebase-b9ad4.firebaseio.com</string>
</dict>
</plist>
</plist>

View File

@ -25,6 +25,7 @@ target 'ReactNativeFirebaseDemo' do
pod 'Firebase/Crash'
pod 'Firebase/Database'
pod 'Firebase/DynamicLinks'
pod 'Firebase/Firestore'
pod 'Firebase/Messaging'
pod 'Firebase/RemoteConfig'
pod 'Firebase/Storage'

View File

@ -1,44 +1,53 @@
PODS:
- Firebase/AdMob (4.2.0):
- BoringSSL (9.0):
- BoringSSL/Implementation (= 9.0)
- BoringSSL/Interface (= 9.0)
- BoringSSL/Implementation (9.0):
- BoringSSL/Interface (= 9.0)
- BoringSSL/Interface (9.0)
- Firebase/AdMob (4.3.0):
- Firebase/Core
- Google-Mobile-Ads-SDK (= 7.24.0)
- Firebase/Auth (4.2.0):
- Google-Mobile-Ads-SDK (= 7.24.1)
- Firebase/Auth (4.3.0):
- Firebase/Core
- FirebaseAuth (= 4.2.0)
- Firebase/Core (4.2.0):
- FirebaseAnalytics (= 4.0.3)
- FirebaseCore (= 4.0.7)
- Firebase/Crash (4.2.0):
- FirebaseAuth (= 4.2.1)
- Firebase/Core (4.3.0):
- FirebaseAnalytics (= 4.0.4)
- FirebaseCore (= 4.0.8)
- Firebase/Crash (4.3.0):
- Firebase/Core
- FirebaseCrash (= 2.0.2)
- Firebase/Database (4.2.0):
- Firebase/Database (4.3.0):
- Firebase/Core
- FirebaseDatabase (= 4.0.3)
- Firebase/DynamicLinks (4.2.0):
- FirebaseDatabase (= 4.1.0)
- Firebase/DynamicLinks (4.3.0):
- Firebase/Core
- FirebaseDynamicLinks (= 2.1.0)
- Firebase/Messaging (4.2.0):
- Firebase/Firestore (4.3.0):
- Firebase/Core
- FirebaseMessaging (= 2.0.3)
- Firebase/Performance (4.2.0):
- FirebaseFirestore (= 0.8.0)
- Firebase/Messaging (4.3.0):
- Firebase/Core
- FirebasePerformance (= 1.0.5)
- Firebase/RemoteConfig (4.2.0):
- FirebaseMessaging (= 2.0.4)
- Firebase/Performance (4.3.0):
- Firebase/Core
- FirebasePerformance (= 1.0.6)
- Firebase/RemoteConfig (4.3.0):
- Firebase/Core
- FirebaseRemoteConfig (= 2.0.3)
- Firebase/Storage (4.2.0):
- Firebase/Storage (4.3.0):
- Firebase/Core
- FirebaseStorage (= 2.0.2)
- FirebaseAnalytics (4.0.3):
- FirebaseAnalytics (4.0.4):
- FirebaseCore (~> 4.0)
- FirebaseInstanceID (~> 2.0)
- GoogleToolboxForMac/NSData+zlib (~> 2.1)
- nanopb (~> 0.3)
- FirebaseAuth (4.2.0):
- FirebaseAuth (4.2.1):
- FirebaseAnalytics (~> 4.0)
- GoogleToolboxForMac/NSDictionary+URLArguments (~> 2.1)
- GTMSessionFetcher/Core (~> 1.1)
- FirebaseCore (4.0.7):
- FirebaseCore (4.0.8):
- GoogleToolboxForMac/NSData+zlib (~> 2.1)
- nanopb (~> 0.3)
- FirebaseCrash (2.0.2):
@ -47,20 +56,27 @@ PODS:
- GoogleToolboxForMac/Logger (~> 2.1)
- GoogleToolboxForMac/NSData+zlib (~> 2.1)
- Protobuf (~> 3.1)
- FirebaseDatabase (4.0.3):
- FirebaseDatabase (4.1.0):
- FirebaseAnalytics (~> 4.0)
- FirebaseCore (~> 4.0)
- leveldb-library (~> 1.18)
- FirebaseDynamicLinks (2.1.0):
- FirebaseAnalytics (~> 4.0)
- FirebaseInstanceID (2.0.3)
- FirebaseMessaging (2.0.3):
- FirebaseFirestore (0.8.0):
- FirebaseAnalytics (~> 4.0)
- FirebaseAuth (~> 4.2)
- FirebaseCore (~> 4.0)
- gRPC-ProtoRPC (~> 1.0)
- leveldb-library (~> 1.18)
- Protobuf (~> 3.1)
- FirebaseInstanceID (2.0.4)
- FirebaseMessaging (2.0.4):
- FirebaseAnalytics (~> 4.0)
- FirebaseCore (~> 4.0)
- FirebaseInstanceID (~> 2.0)
- GoogleToolboxForMac/Logger (~> 2.1)
- Protobuf (~> 3.1)
- FirebasePerformance (1.0.5):
- FirebasePerformance (1.0.6):
- FirebaseAnalytics (~> 4.0)
- FirebaseInstanceID (~> 2.0)
- GoogleToolboxForMac/Logger (~> 2.1)
@ -76,7 +92,7 @@ PODS:
- FirebaseAnalytics (~> 4.0)
- FirebaseCore (~> 4.0)
- GTMSessionFetcher/Core (~> 1.1)
- Google-Mobile-Ads-SDK (7.24.0)
- Google-Mobile-Ads-SDK (7.24.1)
- GoogleToolboxForMac/DebugUtils (2.1.1):
- GoogleToolboxForMac/Defines (= 2.1.1)
- GoogleToolboxForMac/Defines (2.1.1)
@ -89,6 +105,22 @@ PODS:
- GoogleToolboxForMac/Defines (= 2.1.1)
- GoogleToolboxForMac/NSString+URLArguments (= 2.1.1)
- GoogleToolboxForMac/NSString+URLArguments (2.1.1)
- gRPC (1.6.0):
- gRPC-Core (= 1.6.0)
- gRPC-RxLibrary (= 1.6.0)
- gRPC-Core (1.6.0):
- gRPC-Core/Implementation (= 1.6.0)
- gRPC-Core/Interface (= 1.6.0)
- gRPC-Core/Implementation (1.6.0):
- BoringSSL (~> 9.0)
- gRPC-Core/Interface (= 1.6.0)
- nanopb (~> 0.3)
- gRPC-Core/Interface (1.6.0)
- gRPC-ProtoRPC (1.6.0):
- gRPC (= 1.6.0)
- gRPC-RxLibrary (= 1.6.0)
- Protobuf (~> 3.0)
- gRPC-RxLibrary (1.6.0)
- GTMSessionFetcher/Core (1.1.12)
- leveldb-library (1.18.3)
- nanopb (0.3.8):
@ -106,9 +138,6 @@ PODS:
- yoga (= 0.49.0-rc.6.React)
- React/cxxreact_legacy (0.49.0-rc.6):
- React/jschelpers_legacy
- React/DevSupport (0.49.0-rc.6):
- React/Core
- React/RCTWebSocket
- React/fishhook (0.49.0-rc.6)
- React/jschelpers_legacy (0.49.0-rc.6)
- React/RCTBlob (0.49.0-rc.6):
@ -132,13 +161,13 @@ DEPENDENCIES:
- Firebase/Crash
- Firebase/Database
- Firebase/DynamicLinks
- Firebase/Firestore
- Firebase/Messaging
- Firebase/Performance
- Firebase/RemoteConfig
- Firebase/Storage
- React/BatchedBridge (from `../node_modules/react-native`)
- React/Core (from `../node_modules/react-native`)
- React/DevSupport (from `../node_modules/react-native`)
- React/RCTNetwork (from `../node_modules/react-native`)
- React/RCTText (from `../node_modules/react-native`)
- React/RCTWebSocket (from `../node_modules/react-native`)
@ -154,28 +183,34 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/ReactCommon/yoga"
SPEC CHECKSUMS:
Firebase: 9548cae14d69718add12d75a5b312893f7ef89c7
FirebaseAnalytics: 76f754d37ca5b04f36856729b6af3ca0152d1069
FirebaseAuth: 22f8a5170f31d1f111141950590f071f35df3229
FirebaseCore: 9a6cc1e3eaf75905390f9220596ad4fd8f92faee
BoringSSL: 19083b821ef3ae0f758fae15482e183003b1e265
Firebase: 83283761a1ef6dc9846e03d08059f51421afbd65
FirebaseAnalytics: 722b53c7b32bfc7806b06e0093a2f5180d4f2c5a
FirebaseAuth: d7f047fbeab98062b98ea933b8d934e0fb1190e2
FirebaseCore: 69b1a5ac5f857ba6d5fd9d5fe794f4786dd5e579
FirebaseCrash: cded0fc566c03651aea606a101bc156085f333ca
FirebaseDatabase: '03940adcac54ce30db06f1fc2136f8581734ce2c'
FirebaseDatabase: 607284a103e961d7f5863ee603cab5e85f443bd6
FirebaseDynamicLinks: ed4cb6c42705aaa5e841ed2d76e3a4bddbec10c1
FirebaseInstanceID: a4fc702b5a026f7322964376047f1a3f1f7cc6ff
FirebaseMessaging: eaf1bfff0193170c04ea3ba3bfe983f68f893118
FirebasePerformance: d0dc2a1d3dc1bca249d154cb40ee4eae25b455ad
FirebaseFirestore: 8e2fd99a621ae6fc6acfac3bdea824fe9d9c128d
FirebaseInstanceID: 70c2b877e9338971b2429ea5a4293df6961aa44e
FirebaseMessaging: 3dd86bfda2acb680b05c97f3f8ac566e9bb87b2a
FirebasePerformance: fa032c27e229eb8c1a8638918793fe2e47465205
FirebaseRemoteConfig: 1c982f73af48ec048c8fa8621d5178cfdffac9aa
FirebaseStorage: 0cca42d9b889a0227c3a50121f45a4469fc9eb27
Google-Mobile-Ads-SDK: f405b7acb098fe89e6fcd05fdbf400c1a5bcb935
Google-Mobile-Ads-SDK: ed8004a7265b424568dc84f3d2bbe3ea3fff958f
GoogleToolboxForMac: 8e329f1b599f2512c6b10676d45736bcc2cbbeb0
gRPC: '07788969b862af21491908f82b83d17ac08c94cd'
gRPC-Core: f707ade59c559fe718e27713189607d03b15f571
gRPC-ProtoRPC: de7505e493a9d1b6b96c8ea8f976c73100fdf53f
gRPC-RxLibrary: 17b9699beb0a838b95b57832244f9ead18e66777
GTMSessionFetcher: ebaa1f79a5366922c1735f1566901f50beba23b7
leveldb-library: 10fb39c39e243db4af1828441162405bbcec1404
nanopb: 5601e6bca2dbf1ed831b519092ec110f66982ca3
Protobuf: 03eef2ee0b674770735cf79d9c4d3659cf6908e8
React: e6ef6a41ec6dd1b7941417d60ca582bf5e9c739d
RNFirebase: 60be8c01b94551a12e7be5431189e8ee8cefcdd3
RNFirebase: 6508ffd6cab78cc3a84305708a250d7d4b74f2dc
yoga: f9485d2ebf0ca773db2d727ea71b1aa8c9f3e075
PODFILE CHECKSUM: de6bb6c2ad60b7ad1f6c01aa01268ec04eb389a9
PODFILE CHECKSUM: b5674be55653f5dda937c8b794d0479900643d45
COCOAPODS: 1.2.1

View File

@ -5,7 +5,6 @@
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */; };
00C302E71ABCBA2D00DB3ED1 /* libRCTGeolocation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302BA1ABCB90400DB3ED1 /* libRCTGeolocation.a */; };
@ -37,6 +36,7 @@
E51DA6317685417F97A59475 /* libRNVectorIcons.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 0C76E33ACF004369AEB318B1 /* libRNVectorIcons.a */; };
EA30CACE4CB84AC1BAE59432 /* Entypo.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 4935BEDE99B9436581953E77 /* Entypo.ttf */; };
F57EC9E3C5414A99821B73F4 /* EvilIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = DB5BB32AF70B41678974C6B4 /* EvilIcons.ttf */; };
58EAF3FA628941C98A02BAE4 /* Feather.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 528FC2DCD44F4567A3FE9F59 /* Feather.ttf */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -322,6 +322,7 @@
CC24EB30F0484352BD65FFC1 /* Octicons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Octicons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Octicons.ttf"; sourceTree = "<group>"; };
DB5BB32AF70B41678974C6B4 /* EvilIcons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = EvilIcons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/EvilIcons.ttf"; sourceTree = "<group>"; };
FD3DFC8253C74B6298AFD3B7 /* SimpleLineIcons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = SimpleLineIcons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/SimpleLineIcons.ttf"; sourceTree = "<group>"; };
528FC2DCD44F4567A3FE9F59 /* Feather.ttf */ = {isa = PBXFileReference; name = "Feather.ttf"; path = "../node_modules/react-native-vector-icons/Fonts/Feather.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -597,6 +598,7 @@
CC24EB30F0484352BD65FFC1 /* Octicons.ttf */,
FD3DFC8253C74B6298AFD3B7 /* SimpleLineIcons.ttf */,
182271FFECD74C3B92960E1D /* Zocial.ttf */,
528FC2DCD44F4567A3FE9F59 /* Feather.ttf */,
);
name = Resources;
sourceTree = "<group>";
@ -990,6 +992,7 @@
885057F5D1FA461AAAE0B487 /* Octicons.ttf in Resources */,
42A0E8F428A74B23B4C7D95A /* SimpleLineIcons.ttf in Resources */,
D46EBD0604CE40EFB18F8A35 /* Zocial.ttf in Resources */,
58EAF3FA628941C98A02BAE4 /* Feather.ttf in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@ -34,7 +34,7 @@
</dict>
</dict>
<key>NSLocationWhenInUseUsageDescription</key>
<string></string>
<string/>
<key>UIAppFonts</key>
<array>
<string>Entypo.ttf</string>
@ -47,6 +47,7 @@
<string>Octicons.ttf</string>
<string>SimpleLineIcons.ttf</string>
<string>Zocial.ttf</string>
<string>Feather.ttf</string>
</array>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>

2605
tests/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -31,7 +31,7 @@ class CoreContainer extends React.Component {
NetInfo.isConnected.fetch().then((isConnected) => {
this.handleAppStateChange('active'); // Force connect (react debugger issue)
this.props.dispatch(setNetworkState(isConnected));
NetInfo.isConnected.addEventListener('change', this.handleNetworkChange);
NetInfo.isConnected.addEventListener('connectionChange', this.handleNetworkChange);
});
}
@ -40,7 +40,7 @@ class CoreContainer extends React.Component {
*/
componentWillUnmount() {
AppState.removeEventListener('change', this.handleAppStateChange);
NetInfo.isConnected.removeEventListener('change', this.handleNetworkChange);
NetInfo.isConnected.removeEventListener('connectionChange', this.handleNetworkChange);
}
props: Props;

View File

@ -0,0 +1,385 @@
import sinon from 'sinon';
import 'should-sinon';
import should from 'should';
function collectionReferenceTests({ describe, it, context, firebase }) {
describe('CollectionReference', () => {
context('class', () => {
it('should return instance methods', () => {
return new Promise((resolve) => {
const collection = firebase.native.firestore().collection('collection-tests');
collection.should.have.property('firestore');
// TODO: Remaining checks
resolve();
});
});
});
context('add()', () => {
it('should create Document', () => {
return firebase.native.firestore()
.collection('collection-tests')
.add({ first: 'Ada', last: 'Lovelace', born: 1815 })
.then(async (docRef) => {
const doc = await firebase.native.firestore().doc(docRef.path).get();
doc.data().first.should.equal('Ada');
});
});
});
context('doc()', () => {
it('should create DocumentReference with correct path', () => {
return new Promise((resolve) => {
const docRef = firebase.native.firestore().collection('collection-tests').doc('doc');
should.equal(docRef.path, 'collection-tests/doc');
resolve();
});
});
});
context('get()', () => {
it('should retrieve a single document', () => {
return firebase.native.firestore()
.collection('collection-tests')
.get()
.then((querySnapshot) => {
should.equal(querySnapshot.size, 1);
querySnapshot.forEach((documentSnapshot) => {
should.equal(documentSnapshot.data().baz, true);
});
});
});
});
context('onSnapshot()', () => {
it('calls callback with the initial data and then when document changes', () => {
return new Promise(async (resolve) => {
const collectionRef = firebase.native.firestore().collection('document-tests');
const currentDocValue = { name: 'doc1' };
const newDocValue = { name: 'updated' };
const callback = sinon.spy();
// Test
let unsubscribe;
await new Promise((resolve2) => {
unsubscribe = collectionRef.onSnapshot((snapshot) => {
snapshot.forEach(doc => callback(doc.data()));
resolve2();
});
});
callback.should.be.calledWith(currentDocValue);
const docRef = firebase.native.firestore().doc('document-tests/doc1');
await docRef.set(newDocValue);
await new Promise((resolve2) => {
setTimeout(() => resolve2(), 5);
});
// Assertions
callback.should.be.calledWith(newDocValue);
callback.should.be.calledTwice();
// Tear down
unsubscribe();
resolve();
});
});
});
context('onSnapshot()', () => {
it('calls callback with the initial data and then when document is added', () => {
return new Promise(async (resolve) => {
const collectionRef = firebase.native.firestore().collection('document-tests');
const currentDocValue = { name: 'doc1' };
const newDocValue = { name: 'updated' };
const callback = sinon.spy();
// Test
let unsubscribe;
await new Promise((resolve2) => {
unsubscribe = collectionRef.onSnapshot((snapshot) => {
snapshot.forEach(doc => callback(doc.data()));
resolve2();
});
});
callback.should.be.calledWith(currentDocValue);
const docRef = firebase.native.firestore().doc('document-tests/doc2');
await docRef.set(newDocValue);
await new Promise((resolve2) => {
setTimeout(() => resolve2(), 5);
});
// Assertions
callback.should.be.calledWith(currentDocValue);
callback.should.be.calledWith(newDocValue);
callback.should.be.calledThrice();
// Tear down
unsubscribe();
resolve();
});
});
});
context('onSnapshot()', () => {
it('doesn\'t call callback when the ref is updated with the same value', async () => {
return new Promise(async (resolve) => {
const collectionRef = firebase.native.firestore().collection('document-tests');
const currentDocValue = { name: 'doc1' };
const callback = sinon.spy();
// Test
let unsubscribe;
await new Promise((resolve2) => {
unsubscribe = collectionRef.onSnapshot((snapshot) => {
snapshot.forEach(doc => callback(doc.data()));
resolve2();
});
});
callback.should.be.calledWith(currentDocValue);
const docRef = firebase.native.firestore().doc('document-tests/doc1');
await docRef.set(currentDocValue);
await new Promise((resolve2) => {
setTimeout(() => resolve2(), 5);
});
// Assertions
callback.should.be.calledOnce(); // Callback is not called again
// Tear down
unsubscribe();
resolve();
});
});
});
context('onSnapshot()', () => {
it('allows binding multiple callbacks to the same ref', () => {
return new Promise(async (resolve) => {
// Setup
const collectionRef = firebase.native.firestore().collection('document-tests');
const currentDocValue = { name: 'doc1' };
const newDocValue = { name: 'updated' };
const callbackA = sinon.spy();
const callbackB = sinon.spy();
// Test
let unsubscribeA;
let unsubscribeB;
await new Promise((resolve2) => {
unsubscribeA = collectionRef.onSnapshot((snapshot) => {
snapshot.forEach(doc => callbackA(doc.data()));
resolve2();
});
});
await new Promise((resolve2) => {
unsubscribeB = collectionRef.onSnapshot((snapshot) => {
snapshot.forEach(doc => callbackB(doc.data()));
resolve2();
});
});
callbackA.should.be.calledWith(currentDocValue);
callbackA.should.be.calledOnce();
callbackB.should.be.calledWith(currentDocValue);
callbackB.should.be.calledOnce();
const docRef = firebase.native.firestore().doc('document-tests/doc1');
await docRef.set(newDocValue);
await new Promise((resolve2) => {
setTimeout(() => resolve2(), 5);
});
callbackA.should.be.calledWith(newDocValue);
callbackB.should.be.calledWith(newDocValue);
callbackA.should.be.calledTwice();
callbackB.should.be.calledTwice();
// Tear down
unsubscribeA();
unsubscribeB();
resolve();
});
});
});
context('onSnapshot()', () => {
it('listener stops listening when unsubscribed', () => {
return new Promise(async (resolve) => {
// Setup
const collectionRef = firebase.native.firestore().collection('document-tests');
const currentDocValue = { name: 'doc1' };
const newDocValue = { name: 'updated' };
const callbackA = sinon.spy();
const callbackB = sinon.spy();
// Test
let unsubscribeA;
let unsubscribeB;
await new Promise((resolve2) => {
unsubscribeA = collectionRef.onSnapshot((snapshot) => {
snapshot.forEach(doc => callbackA(doc.data()));
resolve2();
});
});
await new Promise((resolve2) => {
unsubscribeB = collectionRef.onSnapshot((snapshot) => {
snapshot.forEach(doc => callbackB(doc.data()));
resolve2();
});
});
callbackA.should.be.calledWith(currentDocValue);
callbackA.should.be.calledOnce();
callbackB.should.be.calledWith(currentDocValue);
callbackB.should.be.calledOnce();
const docRef = firebase.native.firestore().doc('document-tests/doc1');
await docRef.set(newDocValue);
await new Promise((resolve2) => {
setTimeout(() => resolve2(), 5);
});
callbackA.should.be.calledWith(newDocValue);
callbackB.should.be.calledWith(newDocValue);
callbackA.should.be.calledTwice();
callbackB.should.be.calledTwice();
// Unsubscribe A
unsubscribeA();
await docRef.set(currentDocValue);
await new Promise((resolve2) => {
setTimeout(() => resolve2(), 5);
});
callbackB.should.be.calledWith(currentDocValue);
callbackA.should.be.calledTwice();
callbackB.should.be.calledThrice();
// Unsubscribe B
unsubscribeB();
await docRef.set(newDocValue);
await new Promise((resolve2) => {
setTimeout(() => resolve2(), 5);
});
callbackA.should.be.calledTwice();
callbackB.should.be.calledThrice();
resolve();
});
});
});
// Where
context('where()', () => {
it('correctly handles == boolean values', () => {
return firebase.native.firestore()
.collection('collection-tests')
.where('baz', '==', true)
.get()
.then((querySnapshot) => {
should.equal(querySnapshot.size, 1);
querySnapshot.forEach((documentSnapshot) => {
should.equal(documentSnapshot.data().baz, true);
});
});
});
it('correctly handles == string values', () => {
return firebase.native.firestore()
.collection('collection-tests')
.where('foo', '==', 'bar')
.get()
.then((querySnapshot) => {
should.equal(querySnapshot.size, 1);
querySnapshot.forEach((documentSnapshot) => {
should.equal(documentSnapshot.data().foo, 'bar');
});
});
});
it('correctly handles == null values', () => {
return firebase.native.firestore()
.collection('collection-tests')
.where('naz', '==', null)
.get()
.then((querySnapshot) => {
should.equal(querySnapshot.size, 1);
querySnapshot.forEach((documentSnapshot) => {
should.equal(documentSnapshot.data().naz, null);
});
});
});
it('correctly handles >= number values', () => {
return firebase.native.firestore()
.collection('collection-tests')
.where('daz', '>=', 123)
.get()
.then((querySnapshot) => {
should.equal(querySnapshot.size, 1);
querySnapshot.forEach((documentSnapshot) => {
should.equal(documentSnapshot.data().daz, 123);
});
});
});
it('correctly handles <= float values', () => {
return firebase.native.firestore()
.collection('collection-tests')
.where('gaz', '<=', 12.1234666)
.get()
.then((querySnapshot) => {
should.equal(querySnapshot.size, 1);
querySnapshot.forEach((documentSnapshot) => {
should.equal(documentSnapshot.data().gaz, 12.1234567);
});
});
});
});
});
}
export default collectionReferenceTests;

View File

@ -0,0 +1,299 @@
import sinon from 'sinon';
import 'should-sinon';
import should from 'should';
function collectionReferenceTests({ describe, it, context, firebase }) {
describe('DocumentReference', () => {
context('class', () => {
it('should return instance methods', () => {
return new Promise((resolve) => {
const document = firebase.native.firestore().doc('document-tests/doc1');
document.should.have.property('firestore');
// TODO: Remaining checks
resolve();
});
});
});
context('delete()', () => {
it('should delete Document', () => {
return firebase.native.firestore()
.doc('document-tests/doc1')
.delete()
.then(async () => {
const doc = await firebase.native.firestore().doc('document-tests/doc1').get();
should.equal(doc.exists, false);
});
});
});
context('onSnapshot()', () => {
it('calls callback with the initial data and then when value changes', () => {
return new Promise(async (resolve) => {
const docRef = firebase.native.firestore().doc('document-tests/doc1');
const currentDataValue = { name: 'doc1' };
const newDataValue = { name: 'updated' };
const callback = sinon.spy();
// Test
let unsubscribe;
await new Promise((resolve2) => {
unsubscribe = docRef.onSnapshot((snapshot) => {
callback(snapshot.data());
resolve2();
});
});
callback.should.be.calledWith(currentDataValue);
// Update the document
await docRef.set(newDataValue);
await new Promise((resolve2) => {
setTimeout(() => resolve2(), 5);
});
// Assertions
callback.should.be.calledWith(newDataValue);
callback.should.be.calledTwice();
// Tear down
unsubscribe();
resolve();
});
});
});
context('onSnapshot()', () => {
it('doesn\'t call callback when the ref is updated with the same value', async () => {
return new Promise(async (resolve) => {
const docRef = firebase.native.firestore().doc('document-tests/doc1');
const currentDataValue = { name: 'doc1' };
const callback = sinon.spy();
// Test
let unsubscribe;
await new Promise((resolve2) => {
unsubscribe = docRef.onSnapshot((snapshot) => {
callback(snapshot.data());
resolve2();
});
});
callback.should.be.calledWith(currentDataValue);
await docRef.set(currentDataValue);
await new Promise((resolve2) => {
setTimeout(() => resolve2(), 5);
});
// Assertions
callback.should.be.calledOnce(); // Callback is not called again
// Tear down
unsubscribe();
resolve();
});
});
});
context('onSnapshot()', () => {
it('allows binding multiple callbacks to the same ref', () => {
return new Promise(async (resolve) => {
// Setup
const docRef = firebase.native.firestore().doc('document-tests/doc1');
const currentDataValue = { name: 'doc1' };
const newDataValue = { name: 'updated' };
const callbackA = sinon.spy();
const callbackB = sinon.spy();
// Test
let unsubscribeA;
let unsubscribeB;
await new Promise((resolve2) => {
unsubscribeA = docRef.onSnapshot((snapshot) => {
callbackA(snapshot.data());
resolve2();
});
});
await new Promise((resolve2) => {
unsubscribeB = docRef.onSnapshot((snapshot) => {
callbackB(snapshot.data());
resolve2();
});
});
callbackA.should.be.calledWith(currentDataValue);
callbackA.should.be.calledOnce();
callbackB.should.be.calledWith(currentDataValue);
callbackB.should.be.calledOnce();
await docRef.set(newDataValue);
await new Promise((resolve2) => {
setTimeout(() => resolve2(), 5);
});
callbackA.should.be.calledWith(newDataValue);
callbackB.should.be.calledWith(newDataValue);
callbackA.should.be.calledTwice();
callbackB.should.be.calledTwice();
// Tear down
unsubscribeA();
unsubscribeB();
resolve();
});
});
});
context('onSnapshot()', () => {
it('listener stops listening when unsubscribed', () => {
return new Promise(async (resolve) => {
// Setup
const docRef = firebase.native.firestore().doc('document-tests/doc1');
const currentDataValue = { name: 'doc1' };
const newDataValue = { name: 'updated' };
const callbackA = sinon.spy();
const callbackB = sinon.spy();
// Test
let unsubscribeA;
let unsubscribeB;
await new Promise((resolve2) => {
unsubscribeA = docRef.onSnapshot((snapshot) => {
callbackA(snapshot.data());
resolve2();
});
});
await new Promise((resolve2) => {
unsubscribeB = docRef.onSnapshot((snapshot) => {
callbackB(snapshot.data());
resolve2();
});
});
callbackA.should.be.calledWith(currentDataValue);
callbackA.should.be.calledOnce();
callbackB.should.be.calledWith(currentDataValue);
callbackB.should.be.calledOnce();
await docRef.set(newDataValue);
await new Promise((resolve2) => {
setTimeout(() => resolve2(), 5);
});
callbackA.should.be.calledWith(newDataValue);
callbackB.should.be.calledWith(newDataValue);
callbackA.should.be.calledTwice();
callbackB.should.be.calledTwice();
// Unsubscribe A
unsubscribeA();
await docRef.set(currentDataValue);
await new Promise((resolve2) => {
setTimeout(() => resolve2(), 5);
});
callbackB.should.be.calledWith(currentDataValue);
callbackA.should.be.calledTwice();
callbackB.should.be.calledThrice();
// Unsubscribe B
unsubscribeB();
await docRef.set(newDataValue);
await new Promise((resolve2) => {
setTimeout(() => resolve2(), 5);
});
callbackA.should.be.calledTwice();
callbackB.should.be.calledThrice();
resolve();
});
});
});
context('set()', () => {
it('should create Document', () => {
return firebase.native.firestore()
.doc('document-tests/doc2')
.set({ name: 'doc2' })
.then(async () => {
const doc = await firebase.native.firestore().doc('document-tests/doc2').get();
doc.data().name.should.equal('doc2');
});
});
});
context('set()', () => {
it('should merge Document', () => {
return firebase.native.firestore()
.doc('document-tests/doc1')
.set({ merge: 'merge' }, { merge: true })
.then(async () => {
const doc = await firebase.native.firestore().doc('document-tests/doc1').get();
doc.data().name.should.equal('doc1');
doc.data().merge.should.equal('merge');
});
});
});
context('set()', () => {
it('should overwrite Document', () => {
return firebase.native.firestore()
.doc('document-tests/doc1')
.set({ name: 'overwritten' })
.then(async () => {
const doc = await firebase.native.firestore().doc('document-tests/doc1').get();
doc.data().name.should.equal('overwritten');
});
});
});
context('update()', () => {
it('should update Document', () => {
return firebase.native.firestore()
.doc('document-tests/doc1')
.set({ name: 'updated' })
.then(async () => {
const doc = await firebase.native.firestore().doc('document-tests/doc1').get();
doc.data().name.should.equal('updated');
});
});
});
});
}
export default collectionReferenceTests;

View File

@ -0,0 +1,63 @@
import should from 'should';
function firestoreTests({ describe, it, context, firebase }) {
describe('firestore()', () => {
context('collection()', () => {
it('should create CollectionReference with the right id', () => {
return new Promise((resolve) => {
const collectionRef = firebase.native.firestore().collection('collection1/doc1/collection2');
should.equal(collectionRef.id, 'collection2');
resolve();
});
});
});
context('doc()', () => {
it('should create DocumentReference with correct path', () => {
return new Promise((resolve) => {
const docRef = firebase.native.firestore().doc('collection1/doc1/collection2/doc2');
should.equal(docRef.path, 'collection1/doc1/collection2/doc2');
resolve();
});
});
});
context('batch()', () => {
it('should create / update / delete as expected', () => {
const ayRef = firebase.native.firestore().collection('firestore-tests').doc('AY');
const lRef = firebase.native.firestore().collection('firestore-tests').doc('LON');
const nycRef = firebase.native.firestore().collection('firestore-tests').doc('NYC');
const sfRef = firebase.native.firestore().collection('firestore-tests').doc('SF');
return firebase.native.firestore()
.batch()
.set(ayRef, { name: 'Aylesbury' })
.set(lRef, { name: 'London' })
.set(nycRef, { name: 'New York City' })
.set(sfRef, { name: 'San Francisco' })
.update(nycRef, { population: 1000000 })
.update(sfRef, { name: 'San Fran' })
.set(lRef, { population: 3000000 }, { merge: true })
.delete(ayRef)
.commit()
.then(async () => {
const ayDoc = await ayRef.get();
should.equal(ayDoc.exists, false);
const lDoc = await lRef.get();
lDoc.data().name.should.equal('London');
lDoc.data().population.should.equal(3000000);
const nycDoc = await nycRef.get();
nycDoc.data().name.should.equal('New York City');
nycDoc.data().population.should.equal(1000000);
const sfDoc = await sfRef.get();
sfDoc.data().name.should.equal('San Fran');
});
});
});
});
}
export default firestoreTests;

View File

@ -0,0 +1,66 @@
import firebase from '../../firebase';
import TestSuite from '../../../lib/TestSuite';
/*
Test suite files
*/
import collectionReferenceTests from './collectionReferenceTests';
import documentReferenceTests from './documentReferenceTests';
import firestoreTests from './firestoreTests';
const suite = new TestSuite('Firestore', 'firebase.firestore()', firebase);
const testGroups = [
collectionReferenceTests,
documentReferenceTests,
firestoreTests,
];
function firestoreTestSuite(testSuite) {
testSuite.beforeEach(async () => {
this.collectionTestsCollection = testSuite.firebase.native.firestore().collection('collection-tests');
this.documentTestsCollection = testSuite.firebase.native.firestore().collection('document-tests');
this.firestoreTestsCollection = testSuite.firebase.native.firestore().collection('firestore-tests');
// Clean the collections in case the last run failed
await cleanCollection(this.collectionTestsCollection);
await cleanCollection(this.documentTestsCollection);
await cleanCollection(this.firestoreTestsCollection);
await this.collectionTestsCollection.add({
baz: true,
daz: 123,
foo: 'bar',
gaz: 12.1234567,
naz: null,
});
await this.documentTestsCollection.doc('doc1').set({
name: 'doc1',
});
});
testSuite.afterEach(async () => {
await cleanCollection(this.collectionTestsCollection);
await cleanCollection(this.documentTestsCollection);
await cleanCollection(this.firestoreTestsCollection);
});
testGroups.forEach((testGroup) => {
testGroup(testSuite);
});
}
/*
Register tests with test suite
*/
suite.addTests(firestoreTestSuite);
export default suite;
/* HELPER FUNCTIONS */
async function cleanCollection(collection) {
const collectionTestsDocs = await collection.get();
const tasks = [];
collectionTestsDocs.forEach(doc => tasks.push(doc.ref.delete()));
await Promise.all(tasks);
}

View File

@ -1,30 +1,32 @@
import { setSuiteStatus, setTestStatus } from '../actions/TestActions';
import analytics from './analytics/index';
import crash from './crash/index';
import core from './core/index';
import database from './database/index';
import messaging from './messaging/index';
import storage from './storage/index';
import auth from './auth/index';
import config from './config/index';
import performance from './perf/index';
import admob from './admob/index';
import analytics from './analytics';
import crash from './crash';
import core from './core';
import database from './database';
import messaging from './messaging';
import storage from './storage';
import auth from './auth';
import config from './config';
import performance from './perf';
import admob from './admob';
import firestore from './firestore';
window.getCoverage = function getCoverage() {
return (JSON.stringify(global.__coverage__));
};
const testSuiteInstances = [
database,
auth,
analytics,
messaging,
crash,
core,
storage,
config,
performance,
admob,
analytics,
auth,
config,
core,
crash,
database,
firestore,
messaging,
performance,
storage,
];
/*