[android] Add the first raft of Android support

This commit is contained in:
Chris Bianca 2017-09-27 12:57:53 +01:00
parent dfd9080281
commit 52b70d58e3
13 changed files with 803 additions and 68 deletions

View File

@ -33,6 +33,10 @@ android {
allprojects { allprojects {
repositories { repositories {
jcenter() jcenter()
mavenLocal()
maven {
url "https://maven.google.com"
}
} }
} }
@ -78,11 +82,12 @@ dependencies {
compile "com.google.android.gms:play-services-base:$firebaseVersion" compile "com.google.android.gms:play-services-base:$firebaseVersion"
compile "com.google.firebase:firebase-core:$firebaseVersion" compile "com.google.firebase:firebase-core:$firebaseVersion"
compile "com.google.firebase:firebase-config:$firebaseVersion" compile "com.google.firebase:firebase-config:$firebaseVersion"
compile "com.google.firebase:firebase-auth:$firebaseVersion" compile "com.google.firebase:firebase-auth:11.3.0"
compile "com.google.firebase:firebase-database:$firebaseVersion" compile "com.google.firebase:firebase-database:$firebaseVersion"
compile "com.google.firebase:firebase-storage:$firebaseVersion" compile "com.google.firebase:firebase-storage:$firebaseVersion"
compile "com.google.firebase:firebase-messaging:$firebaseVersion" compile "com.google.firebase:firebase-messaging:$firebaseVersion"
compile "com.google.firebase:firebase-crash:$firebaseVersion" compile "com.google.firebase:firebase-crash:$firebaseVersion"
compile "com.google.firebase:firebase-perf:$firebaseVersion" compile "com.google.firebase:firebase-perf:$firebaseVersion"
compile "com.google.firebase:firebase-ads:$firebaseVersion" compile "com.google.firebase:firebase-ads:$firebaseVersion"
compile 'com.google.firebase:firebase-firestore:11.3.0'
} }

View File

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

View File

@ -0,0 +1,195 @@
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.getId());
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();
if (!documentChanges.isEmpty()) {
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()));
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,136 @@
package io.invertase.firebase.firestore;
import android.support.annotation.NonNull;
import android.util.Log;
import com.facebook.react.bridge.Promise;
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.Query;
import com.google.firebase.firestore.QuerySnapshot;
import java.util.List;
import java.util.Map;
import io.invertase.firebase.Utils;
public class RNFirebaseCollectionReference {
private static final String TAG = "RNFBCollectionReference";
private final String appName;
private final String path;
private final ReadableArray filters;
private final ReadableArray orders;
private final ReadableMap options;
private final Query query;
RNFirebaseCollectionReference(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();
}
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, task.getException());
}
}
});
}
private Query buildQuery() {
Query query = RNFirebaseFirestore.getFirestoreForApp(appName).collection(path);
query = applyFilters(query, filters);
query = applyOrders(query, orders);
query = applyOptions(query, options);
return query;
}
private Query applyFilters(Query query, ReadableArray filters) {
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, ReadableArray orders) {
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, ReadableMap options) {
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;
}
}

View File

@ -0,0 +1,113 @@
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.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.SetOptions;
import java.util.Map;
import io.invertase.firebase.Utils;
public class RNFirebaseDocumentReference {
private static final String TAG = "RNFBDocumentReference";
private final String appName;
private final String path;
private final DocumentReference ref;
RNFirebaseDocumentReference(String appName, String path) {
this.appName = appName;
this.path = path;
this.ref = RNFirebaseFirestore.getFirestoreForApp(appName).document(path);
}
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, 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, task.getException());
}
}
});
}
public void set(final ReadableMap data, final ReadableMap options, final Promise promise) {
Map<String, Object> map = Utils.recursivelyDeconstructReadableMap(data);
SetOptions setOptions = null;
if (options != null && options.hasKey("merge") && options.getBoolean("merge")) {
setOptions = SetOptions.merge();
}
this.ref.set(map, setOptions).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, task.getException());
}
}
});
}
public void update(final ReadableMap data, final ReadableMap options, 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, task.getException());
}
}
});
}
public void collections(Promise promise) {
// Not supported on Android out of the box
}
}

View File

@ -0,0 +1,223 @@
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.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.SetOptions;
import com.google.firebase.firestore.WriteBatch;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import io.invertase.firebase.Utils;
public class RNFirebaseFirestore extends ReactContextBaseJavaModule {
private static final String TAG = "RNFirebaseFirestore";
// private HashMap<String, RNFirebaseDatabaseReference> references = new HashMap<>();
// 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) {
RNFirebaseCollectionReference ref = getCollectionForAppPath(appName, path, filters, orders, options);
ref.get(promise);
}
@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");
SetOptions setOptions = null;
if (options != null && options.containsKey("merge") && (boolean)options.get("merge")) {
setOptions = SetOptions.merge();
}
batch = batch.set(ref, data, setOptions);
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, task.getException());
}
}
});
}
@ReactMethod
public void documentCollections(String appName, String path, final Promise promise) {
RNFirebaseDocumentReference ref = getDocumentForAppPath(appName, path);
ref.collections(promise);
}
@ReactMethod
public void documentCreate(String appName, String path, ReadableMap data, final Promise promise) {
RNFirebaseDocumentReference ref = getDocumentForAppPath(appName, path);
ref.create(data, promise);
}
@ReactMethod
public void documentDelete(String appName, String path, ReadableMap options, final Promise promise) {
RNFirebaseDocumentReference ref = getDocumentForAppPath(appName, path);
ref.delete(options, promise);
}
@ReactMethod
public void documentGet(String appName, String path, final Promise promise) {
RNFirebaseDocumentReference 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 documentSet(String appName, String path, ReadableMap data, ReadableMap options, final Promise promise) {
RNFirebaseDocumentReference ref = getDocumentForAppPath(appName, path);
ref.set(data, options, promise);
}
@ReactMethod
public void documentUpdate(String appName, String path, ReadableMap data, ReadableMap options, final Promise promise) {
RNFirebaseDocumentReference ref = getDocumentForAppPath(appName, path);
ref.update(data, options, 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, Exception exception) {
// TODO
// WritableMap jsError = getJSError(exception);
promise.reject(
"TODO", // jsError.getString("code"),
exception.getMessage(), // 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 RNFirebaseCollectionReference getCollectionForAppPath(String appName, String path,
ReadableArray filters,
ReadableArray orders,
ReadableMap options) {
return new RNFirebaseCollectionReference(appName, path, filters, orders, options);
}
/**
* Get a document reference for a specific app and path
*
* @param appName
* @param path
* @return
*/
private RNFirebaseDocumentReference getDocumentForAppPath(String appName, String path) {
return new RNFirebaseDocumentReference(appName, path);
}
/**
* 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());
constants.put("serverTimestampFieldValue", FieldValue.serverTimestamp());
return constants;
}
}

View File

@ -0,0 +1,51 @@
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;
}
/**
* @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}
*/
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}

View File

@ -5,19 +5,14 @@
import CollectionReference from './CollectionReference'; import CollectionReference from './CollectionReference';
import DocumentSnapshot from './DocumentSnapshot'; import DocumentSnapshot from './DocumentSnapshot';
import Path from './Path'; import Path from './Path';
import INTERNALS from './../../internals';
export type DeleteOptions = { export type DeleteOptions = {
lastUpdateTime?: string, lastUpdateTime?: string,
} }
export type UpdateOptions = {
createIfMissing?: boolean,
lastUpdateTime?: string,
}
export type WriteOptions = { export type WriteOptions = {
createIfMissing?: boolean, merge?: boolean,
lastUpdateTime?: string,
} }
export type WriteResult = { export type WriteResult = {
@ -63,24 +58,25 @@ export default class DocumentReference {
} }
create(data: { [string]: any }): Promise<WriteResult> { create(data: { [string]: any }): Promise<WriteResult> {
return this._firestore._native /* return this._firestore._native
.documentCreate(this._documentPath._parts, data); .documentCreate(this.path, data); */
throw new Error(INTERNALS.STRINGS.ERROR_UNSUPPORTED_CLASS_METHOD('DocumentReference', 'create'));
} }
delete(deleteOptions?: DeleteOptions): Promise<WriteResult> { delete(deleteOptions?: DeleteOptions): Promise<WriteResult> {
return this._firestore._native return this._firestore._native
.documentDelete(this._documentPath._parts, deleteOptions); .documentDelete(this.path, deleteOptions);
} }
get(): Promise<DocumentSnapshot> { get(): Promise<DocumentSnapshot> {
return this._firestore._native return this._firestore._native
.documentGet(this._documentPath._parts) .documentGet(this.path)
.then(result => new DocumentSnapshot(this._firestore, result)); .then(result => new DocumentSnapshot(this._firestore, result));
} }
getCollections(): Promise<CollectionReference[]> { getCollections(): Promise<CollectionReference[]> {
return this._firestore._native /* return this._firestore._native
.documentCollections(this._documentPath._parts) .documentCollections(this.path)
.then((collectionIds) => { .then((collectionIds) => {
const collections = []; const collections = [];
@ -89,7 +85,8 @@ export default class DocumentReference {
} }
return collections; return collections;
}); }); */
throw new Error(INTERNALS.STRINGS.ERROR_UNSUPPORTED_CLASS_METHOD('DocumentReference', 'getCollections'));
} }
onSnapshot(onNext: () => any, onError?: () => any): () => void { onSnapshot(onNext: () => any, onError?: () => any): () => void {
@ -98,12 +95,13 @@ export default class DocumentReference {
set(data: { [string]: any }, writeOptions?: WriteOptions): Promise<WriteResult> { set(data: { [string]: any }, writeOptions?: WriteOptions): Promise<WriteResult> {
return this._firestore._native return this._firestore._native
.documentSet(this._documentPath._parts, data, writeOptions); .documentSet(this.path, data, writeOptions);
} }
update(data: { [string]: any }, updateOptions?: UpdateOptions): Promise<WriteResult> { // TODO: Update to new update method signature
update(data: { [string]: any }): Promise<WriteResult> {
return this._firestore._native return this._firestore._native
.documentUpdate(this._documentPath._parts, data, updateOptions); .documentUpdate(this.path, data);
} }
/** /**
@ -117,6 +115,6 @@ export default class DocumentReference {
* @private * @private
*/ */
_getDocumentKey() { _getDocumentKey() {
return `$${this._firestore._appName}$/${this._documentPath._parts.join('/')}`; return `$${this._firestore._appName}$/${this.path}`;
} }
} }

View File

@ -4,11 +4,12 @@
*/ */
import DocumentReference from './DocumentReference'; import DocumentReference from './DocumentReference';
import Path from './Path'; import Path from './Path';
import INTERNALS from './../../internals';
export type DocumentSnapshotNativeData = { export type DocumentSnapshotNativeData = {
createTime: string, createTime: string,
data: Object, data: Object,
name: string, path: string,
readTime: string, readTime: string,
updateTime: string, updateTime: string,
} }
@ -26,13 +27,14 @@ export default class DocumentSnapshot {
constructor(firestore: Object, nativeData: DocumentSnapshotNativeData) { constructor(firestore: Object, nativeData: DocumentSnapshotNativeData) {
this._createTime = nativeData.createTime; this._createTime = nativeData.createTime;
this._data = nativeData.data; this._data = nativeData.data;
this._ref = new DocumentReference(firestore, Path.fromName(nativeData.name)); this._ref = new DocumentReference(firestore, Path.fromName(nativeData.path));
this._readTime = nativeData.readTime; this._readTime = nativeData.readTime;
this._updateTime = nativeData.updateTime; this._updateTime = nativeData.updateTime;
} }
get createTime(): string { get createTime(): string {
return this._createTime; // return this._createTime;
throw new Error(INTERNALS.STRINGS.ERROR_UNSUPPORTED_CLASS_PROPERTY('DocumentSnapshot', 'createTime'));
} }
get exists(): boolean { get exists(): boolean {
@ -44,7 +46,8 @@ export default class DocumentSnapshot {
} }
get readTime(): string { get readTime(): string {
return this._readTime; // return this._readTime;
throw new Error(INTERNALS.STRINGS.ERROR_UNSUPPORTED_CLASS_PROPERTY('DocumentSnapshot', 'readTime'));
} }
get ref(): DocumentReference { get ref(): DocumentReference {
@ -52,7 +55,8 @@ export default class DocumentSnapshot {
} }
get updateTime(): string { get updateTime(): string {
return this._updateTime; // return this._updateTime;
throw new Error(INTERNALS.STRINGS.ERROR_UNSUPPORTED_CLASS_PROPERTY('DocumentSnapshot', 'updateTime'));
} }
data(): Object { data(): Object {

View File

@ -5,22 +5,23 @@
import DocumentSnapshot from './DocumentSnapshot'; import DocumentSnapshot from './DocumentSnapshot';
import Path from './Path'; import Path from './Path';
import QuerySnapshot from './QuerySnapshot'; import QuerySnapshot from './QuerySnapshot';
import INTERNALS from './../../internals';
const DIRECTIONS = { const DIRECTIONS = {
DESC: 'descending', ASC: 'ASCENDING',
desc: 'descending', asc: 'ASCENDING',
ASC: 'ascending', DESC: 'DESCENDING',
asc: 'ascending' desc: 'DESCENDING',
}; };
const DOCUMENT_NAME_FIELD = '__name__'; const DOCUMENT_NAME_FIELD = '__name__';
const OPERATORS = { const OPERATORS = {
'<': 'LESS_THAN',
'<=': 'LESS_THAN_OR_EQUAL',
'=': 'EQUAL', '=': 'EQUAL',
'==': 'EQUAL', '==': 'EQUAL',
'>': 'GREATER_THAN', '>': 'GREATER_THAN',
'>=': 'GREATER_THAN_OR_EQUAL', '>=': 'GREATER_THAN_OR_EQUAL',
'<': 'LESS_THAN',
'<=': 'LESS_THAN_OR_EQUAL',
}; };
export type Direction = 'DESC' | 'desc' | 'ASC' | 'asc'; export type Direction = 'DESC' | 'desc' | 'ASC' | 'asc';
@ -34,6 +35,8 @@ type FieldOrder = {
fieldPath: string, fieldPath: string,
} }
type QueryOptions = { type QueryOptions = {
endAt?: any[],
endBefore?: any[],
limit?: number, limit?: number,
offset?: number, offset?: number,
selectFields?: string[], selectFields?: string[],
@ -92,12 +95,12 @@ export default class Query {
get(): Promise<QuerySnapshot> { get(): Promise<QuerySnapshot> {
return this._firestore._native return this._firestore._native
.collectionGet( .collectionGet(
this._referencePath._parts, this._referencePath.relativeName,
this._fieldFilters, this._fieldFilters,
this._fieldOrders, this._fieldOrders,
this._queryOptions, this._queryOptions,
) )
.then(nativeData => new QuerySnapshot(nativeData)); .then(nativeData => new QuerySnapshot(this._firestore, this, nativeData));
} }
limit(n: number): Query { limit(n: number): Query {
@ -116,12 +119,13 @@ export default class Query {
// TODO: Validation // TODO: Validation
// validate.isInteger('n', n); // validate.isInteger('n', n);
const options = { /* const options = {
...this._queryOptions, ...this._queryOptions,
offset: n, offset: n,
}; };
return new Query(this.firestore, this._referencePath, this._fieldFilters, return new Query(this.firestore, this._referencePath, this._fieldFilters,
this._fieldOrders, options); this._fieldOrders, options); */
throw new Error(INTERNALS.STRINGS.ERROR_UNSUPPORTED_CLASS_METHOD('Query', 'offset'));
} }
onSnapshot(onNext: () => any, onError?: () => any): () => void { onSnapshot(onNext: () => any, onError?: () => any): () => void {
@ -129,9 +133,9 @@ export default class Query {
} }
orderBy(fieldPath: string, directionStr?: Direction = 'asc'): Query { orderBy(fieldPath: string, directionStr?: Direction = 'asc'): Query {
//TODO: Validation // TODO: Validation
//validate.isFieldPath('fieldPath', fieldPath); // validate.isFieldPath('fieldPath', fieldPath);
//validate.isOptionalFieldOrder('directionStr', directionStr); // validate.isOptionalFieldOrder('directionStr', directionStr);
if (this._queryOptions.startAt || this._queryOptions.endAt) { if (this._queryOptions.startAt || this._queryOptions.endAt) {
throw new Error('Cannot specify an orderBy() constraint after calling ' + throw new Error('Cannot specify an orderBy() constraint after calling ' +
@ -148,6 +152,7 @@ export default class Query {
} }
select(varArgs: string[]): Query { select(varArgs: string[]): Query {
/*
varArgs = Array.isArray(arguments[0]) ? arguments[0] : [].slice.call(arguments); varArgs = Array.isArray(arguments[0]) ? arguments[0] : [].slice.call(arguments);
const fieldReferences = []; const fieldReferences = [];
@ -167,7 +172,8 @@ export default class Query {
}; };
return new Query(this.firestore, this._referencePath, this._fieldFilters, return new Query(this.firestore, this._referencePath, this._fieldFilters,
this._fieldOrders, options); this._fieldOrders, options);*/
throw new Error(INTERNALS.STRINGS.ERROR_UNSUPPORTED_CLASS_METHOD('Query', 'select'));
} }
startAfter(fieldValues: any): Query { startAfter(fieldValues: any): Query {

View File

@ -4,7 +4,7 @@
*/ */
import DocumentReference from './DocumentReference'; import DocumentReference from './DocumentReference';
import type { DeleteOptions, UpdateOptions, WriteOptions, WriteResult } from './DocumentReference'; import type { DeleteOptions, WriteOptions, WriteResult } from './DocumentReference';
type CommitOptions = { type CommitOptions = {
transactionId: string, transactionId: string,
@ -13,8 +13,8 @@ type CommitOptions = {
type DocumentWrite = { type DocumentWrite = {
data?: Object, data?: Object,
options?: Object, options?: Object,
path: string[], path: string,
type: 'delete' | 'set' | 'update', type: 'DELETE' | 'SET' | 'UPDATE',
} }
/** /**
@ -51,8 +51,8 @@ export default class WriteBatch {
// validate.isOptionalPrecondition('deleteOptions', deleteOptions); // validate.isOptionalPrecondition('deleteOptions', deleteOptions);
this._writes.push({ this._writes.push({
options: deleteOptions, options: deleteOptions,
path: docRef._documentPath._parts, path: docRef.path,
type: 'delete', type: 'DELETE',
}); });
return this; return this;
@ -67,40 +67,25 @@ export default class WriteBatch {
this._writes.push({ this._writes.push({
data, data,
options: writeOptions, options: writeOptions,
path: docRef._documentPath._parts, path: docRef.path,
type: 'set', type: 'SET',
}); });
// TODO: DocumentTransform ?!
// let documentTransform = DocumentTransform.fromObject(docRef, data);
// if (!documentTransform.isEmpty) {
// this._writes.push({transform: documentTransform.toProto()});
// }
return this; return this;
} }
update(docRef: DocumentReference, data: Object, updateOptions: UpdateOptions): WriteBatch { // TODO: Update to new method signature
update(docRef: DocumentReference, data: { [string]: any }): WriteBatch {
// TODO: Validation // TODO: Validation
// validate.isDocumentReference('docRef', docRef); // validate.isDocumentReference('docRef', docRef);
// validate.isDocument('data', data, true); // validate.isDocument('data', data, true);
// validate.isOptionalPrecondition('updateOptions', updateOptions);
this._writes.push({ this._writes.push({
data, data,
options: updateOptions, path: docRef.path,
path: docRef._documentPath._parts, type: 'UPDATE',
type: 'update',
}); });
// TODO: DocumentTransform ?!
// let documentTransform = DocumentTransform.fromObject(docRef, expandedObject);
// if (!documentTransform.isEmpty) {
// this._writes.push({transform: documentTransform.toProto()});
// }
return this; return this;
} }

View File

@ -11,6 +11,7 @@ import DocumentSnapshot from './DocumentSnapshot';
import GeoPoint from './GeoPoint'; import GeoPoint from './GeoPoint';
import Path from './Path'; import Path from './Path';
import WriteBatch from './WriteBatch'; import WriteBatch from './WriteBatch';
import INTERNALS from './../../internals';
const unquotedIdentifier_ = '(?:[A-Za-z_][A-Za-z_0-9]*)'; const unquotedIdentifier_ = '(?:[A-Za-z_][A-Za-z_0-9]*)';
const UNQUOTED_IDENTIFIER_REGEX = new RegExp(`^${unquotedIdentifier_}$`); const UNQUOTED_IDENTIFIER_REGEX = new RegExp(`^${unquotedIdentifier_}$`);
@ -62,17 +63,18 @@ export default class Firestore extends ModuleBase {
} }
getAll(varArgs: DocumentReference[]): Promise<DocumentSnapshot[]> { getAll(varArgs: DocumentReference[]): Promise<DocumentSnapshot[]> {
varArgs = Array.isArray(arguments[0]) ? arguments[0] : [].slice.call(arguments); /*varArgs = Array.isArray(arguments[0]) ? arguments[0] : [].slice.call(arguments);
const documents = []; const documents = [];
varArgs.forEach((document) => { varArgs.forEach((document) => {
// TODO: Validation // TODO: Validation
// validate.isDocumentReference(i, varArgs[i]); // validate.isDocumentReference(i, varArgs[i]);
documents.push(document._documentPath._parts); documents.push(document.path);
}); });
return this._native return this._native
.documentGetAll(documents) .documentGetAll(documents)
.then(results => results.map(result => new DocumentSnapshot(this, result))); .then(results => results.map(result => new DocumentSnapshot(this, result)));*/
throw new Error(INTERNALS.STRINGS.ERROR_UNSUPPORTED_MODULE_METHOD('Query', 'offset'));
} }
getCollections(): Promise<CollectionReference[]> { getCollections(): Promise<CollectionReference[]> {

View File

@ -9,6 +9,14 @@ const AUTO_ID_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234
const hasOwnProperty = Object.hasOwnProperty; const hasOwnProperty = Object.hasOwnProperty;
const DEFAULT_CHUNK_SIZE = 50; 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. * Deep get a value from an object.
* @website https://github.com/Salakar/deeps * @website https://github.com/Salakar/deeps
@ -88,6 +96,16 @@ export function isString(value): Boolean {
return typeof value === 'string'; 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 // platform checks
export const isIOS = Platform.OS === 'ios'; export const isIOS = Platform.OS === 'ios';
export const isAndroid = Platform.OS === 'android'; export const isAndroid = Platform.OS === 'android';