2
0
mirror of synced 2025-01-11 14:44:12 +00:00

[firestore][android] Start work on type conversion to support DocumentReference, GeoPoint, Date, etc

This commit is contained in:
Chris Bianca 2017-10-08 19:40:46 +01:00
parent 04921663ea
commit fac2272ac3
6 changed files with 245 additions and 50 deletions

View File

@ -1,18 +1,33 @@
package io.invertase.firebase.firestore;
import android.util.Log;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableMapKeySetIterator;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.google.firebase.firestore.DocumentChange;
import com.google.firebase.firestore.DocumentReference;
import com.google.firebase.firestore.DocumentSnapshot;
import com.google.firebase.firestore.FirebaseFirestore;
import com.google.firebase.firestore.GeoPoint;
import com.google.firebase.firestore.QuerySnapshot;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class FirestoreSerialize {
private static final String TAG = "FirestoreSerialize";
private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
private static final String KEY_CHANGES = "changes";
private static final String KEY_DATA = "data";
private static final String KEY_DOC_CHANGE_DOCUMENT = "document";
@ -123,7 +138,8 @@ public class FirestoreSerialize {
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());
WritableMap typeMap = buildTypeMap(entry.getValue());
writableMap.putMap(entry.getKey(), typeMap);
}
return writableMap;
}
@ -138,73 +154,127 @@ public class FirestoreSerialize {
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 || itemClass == ArrayList.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);
}
WritableMap typeMap = buildTypeMap(item);
writableArray.pushMap(typeMap);
}
return writableArray;
}
/**
* Detects an objects type and calls the relevant WritableMap setter method to add the value.
* Detects an objects type and creates a Map to represent the type and value.
*
* @param map WritableMap
* @param key String
* @param value Object
*/
static void putValue(WritableMap map, String key, Object value) {
private static WritableMap buildTypeMap(Object value) {
WritableMap typeMap = Arguments.createMap();
if (value == null) {
map.putNull(key);
typeMap.putString("type", "null");
typeMap.putNull("value");
} else {
Class valueClass = value.getClass();
if (valueClass == Boolean.class) {
map.putBoolean(key, (Boolean) value);
typeMap.putString("type", "boolean");
typeMap.putBoolean("value", (Boolean) value);
} else if (valueClass == Integer.class) {
map.putDouble(key, ((Integer) value).doubleValue());
typeMap.putString("type", "number");
typeMap.putDouble("value", ((Integer) value).doubleValue());
} else if (valueClass == Double.class) {
map.putDouble(key, (Double) value);
typeMap.putString("type", "number");
typeMap.putDouble("value", (Double) value);
} else if (valueClass == Float.class) {
map.putDouble(key, ((Float) value).doubleValue());
typeMap.putString("type", "number");
typeMap.putDouble("value", ((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)));
typeMap.putString("type", "string");
typeMap.putString("value", value.toString());
} else if (valueClass == Map.class || valueClass == HashMap.class) {
typeMap.putString("type", "object");
typeMap.putMap("value", (objectMapToWritable((Map<String, Object>) value)));
} else if (valueClass == Arrays.class) {
map.putArray(key, objectArrayToWritable((Object[]) value));
typeMap.putString("type", "array");
typeMap.putArray("value", objectArrayToWritable((Object[]) value));
} else if (valueClass == List.class || valueClass == ArrayList.class) {
typeMap.putString("type", "array");
List<Object> list = (List<Object>) value;
Object[] array = list.toArray(new Object[list.size()]);
map.putArray(key, objectArrayToWritable(array));
typeMap.putArray("value", objectArrayToWritable(array));
} else if (valueClass == DocumentReference.class) {
typeMap.putString("type", "reference");
typeMap.putString("value", ((DocumentReference) value).getPath());
} else if (valueClass == GeoPoint.class) {
typeMap.putString("type", "geopoint");
WritableMap geoPoint = Arguments.createMap();
geoPoint.putDouble("latitude", ((GeoPoint) value).getLatitude());
geoPoint.putDouble("longitude", ((GeoPoint) value).getLongitude());
typeMap.putMap("value", geoPoint);
} else if (valueClass == Date.class) {
typeMap.putString("type", "date");
typeMap.putString("value", DATE_FORMAT.format((Date) value));
} else {
throw new RuntimeException("Cannot convert object of type " + value);
Log.e(TAG, "buildTypeMap", new RuntimeException("Cannot convert object of type " + valueClass));
typeMap.putString("type", "null");
typeMap.putNull("value");
}
}
return typeMap;
}
static Map<String, Object> parseReadableMap(FirebaseFirestore firestore, ReadableMap readableMap) {
Map<String, Object> map = new HashMap<>();
if (readableMap != null) {
ReadableMapKeySetIterator iterator = readableMap.keySetIterator();
while (iterator.hasNextKey()) {
String key = iterator.nextKey();
map.put(key, parseTypeMap(firestore, readableMap.getMap(key)));
}
}
return map;
}
static List<Object> parseReadableArray(FirebaseFirestore firestore, ReadableArray readableArray) {
List<Object> list = new ArrayList<>();
if (readableArray != null) {
for (int i = 0; i < readableArray.size(); i++) {
list.add(parseTypeMap(firestore, readableArray.getMap(i)));
}
}
return list;
}
static Object parseTypeMap(FirebaseFirestore firestore, ReadableMap typeMap) {
String type = typeMap.getString("type");
if ("boolean".equals(type)) {
return typeMap.getBoolean("value");
} else if ("number".equals(type)) {
return typeMap.getDouble("value");
} else if ("string".equals(type)) {
return typeMap.getString("value");
} else if ("null".equals(type)) {
return null;
} else if ("array".equals(type)) {
return parseReadableArray(firestore, typeMap.getArray("value"));
} else if ("object".equals(type)) {
return parseReadableMap(firestore, typeMap.getMap("value"));
} else if ("reference".equals(type)) {
String path = typeMap.getString("value");
return firestore.document(path);
} else if ("geopoint".equals(type)) {
ReadableMap geoPoint = typeMap.getMap("value");
return new GeoPoint(geoPoint.getDouble("latitude"), geoPoint.getDouble("longitude"));
} else if ("date".equals(type)) {
try {
String date = typeMap.getString("value");
return DATE_FORMAT.parse(date);
} catch (ParseException exception) {
Log.e(TAG, "parseTypeMap", exception);
return null;
}
} else {
Log.e(TAG, "parseTypeMap", new RuntimeException("Cannot convert object of type " + type));
return null;
}
}
}

View File

@ -112,9 +112,8 @@ public class RNFirebaseFirestoreDocumentReference {
}
public void set(final ReadableMap data, final ReadableMap options, final Promise promise) {
Map<String, Object> map = Utils.recursivelyDeconstructReadableMap(data);
Map<String, Object> map = FirestoreSerialize.parseReadableMap(RNFirebaseFirestore.getFirestoreForApp(appName), data);
Task<Void> task;
SetOptions setOptions = null;
if (options != null && options.hasKey("merge") && options.getBoolean("merge")) {
task = this.ref.set(map, SetOptions.merge());
} else {
@ -135,7 +134,7 @@ public class RNFirebaseFirestoreDocumentReference {
}
public void update(final ReadableMap data, final Promise promise) {
Map<String, Object> map = Utils.recursivelyDeconstructReadableMap(data);
Map<String, Object> map = FirestoreSerialize.parseReadableMap(RNFirebaseFirestore.getFirestoreForApp(appName), data);
this.ref.update(map).addOnCompleteListener(new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull Task<Void> task) {

View File

@ -4,8 +4,9 @@
*/
import CollectionReference from './CollectionReference';
import DocumentSnapshot from './DocumentSnapshot';
import GeoPoint from './GeoPoint';
import Path from './Path';
import { firestoreAutoId, isFunction, isObject, isString } from '../../utils';
import { firestoreAutoId, isFunction, isObject, isString, typeOf } from '../../utils';
export type WriteOptions = {
merge?: boolean,
@ -152,8 +153,9 @@ export default class DocumentReference {
}
set(data: Object, writeOptions?: WriteOptions): Promise<void> {
const nativeData = this._buildNativeMap(data);
return this._firestore._native
.documentSet(this.path, data, writeOptions);
.documentSet(this.path, nativeData, writeOptions);
}
update(...args: Object | string[]): Promise<void> {
@ -179,6 +181,10 @@ export default class DocumentReference {
.documentUpdate(this.path, data);
}
/**
* INTERNALS
*/
/**
* Remove document snapshot listener
* @param listener
@ -190,4 +196,59 @@ export default class DocumentReference {
this._firestore._native
.documentOffSnapshot(this.path, listenerId);
}
_buildNativeMap(data: Object): Object {
const nativeData = {};
if (data) {
Object.keys(data).forEach((key) => {
nativeData[key] = this._buildTypeMap(data[key]);
});
}
return nativeData;
}
_buildNativeArray(array: Object[]): any[] {
const nativeArray = [];
if (array) {
array.forEach((value) => {
nativeArray.push(this._buildTypeMap(value));
});
}
return nativeArray;
}
_buildTypeMap(value: any): any {
const typeMap = {};
const type = typeOf(value);
if (value === null) {
typeMap.type = 'null';
typeMap.value = null;
} else if (type === 'boolean' || type === 'number' || type === 'string') {
typeMap.type = type;
typeMap.value = value;
} else if (type === 'array') {
typeMap.type = type;
typeMap.value = this._buildNativeArray(value);
} else if (type === 'object') {
if (value instanceof DocumentReference) {
typeMap.type = 'reference';
typeMap.value = value.path;
} else if (value instanceof GeoPoint) {
typeMap.type = 'geopoint';
typeMap.value = {
latititude: value.latitude,
longitude: value.longitude,
};
} else if (value instanceof Date) {
typeMap.type = 'date';
typeMap.value = value.toISOString();
} else {
typeMap.type = 'object';
typeMap.value = this._buildNativeMap(value);
}
} else {
console.warn(`Unknown data type received ${type}`);
}
return typeMap;
}
}

View File

@ -3,6 +3,7 @@
* DocumentSnapshot representation wrapper
*/
import DocumentReference from './DocumentReference';
import GeoPoint from './GeoPoint';
import Path from './Path';
export type SnapshotMetadata = {
@ -16,16 +17,23 @@ export type DocumentSnapshotNativeData = {
path: string,
}
type TypeMap = {
type: 'array' | 'boolean' | 'geopoint' | 'null' | 'number' | 'object' | 'reference' | 'string',
value: any,
}
/**
* @class DocumentSnapshot
*/
export default class DocumentSnapshot {
_data: Object;
_firestore: Object;
_metadata: SnapshotMetadata;
_ref: DocumentReference;
constructor(firestore: Object, nativeData: DocumentSnapshotNativeData) {
this._data = nativeData.data;
this._data = this._parseNativeMap(nativeData.data);
this._firestore = firestore;
this._metadata = nativeData.metadata;
this._ref = new DocumentReference(firestore, Path.fromName(nativeData.path));
}
@ -53,4 +61,47 @@ export default class DocumentSnapshot {
get(fieldPath: string): any {
return this._data[fieldPath];
}
/**
* INTERNALS
*/
_parseNativeMap(nativeData: Object): Object {
const data = {};
if (nativeData) {
Object.keys(nativeData).forEach((key) => {
data[key] = this._parseTypeMap(nativeData[key]);
});
}
return data;
}
_parseNativeArray(nativeArray: Object[]): any[] {
const array = [];
if (nativeArray) {
nativeArray.forEach((typeMap) => {
array.push(this._parseTypeMap(typeMap));
});
}
return array;
}
_parseTypeMap(typeMap: TypeMap): any {
const { type, value } = typeMap;
if (type === 'boolean' || type === 'number' || type === 'string' || type === 'null') {
return value;
} else if (type === 'array') {
return this._parseNativeArray(value);
} else if (type === 'object') {
return this._parseNativeMap(value);
} else if (type === 'reference') {
return new DocumentReference(this._firestore, Path.fromName(value));
} else if (type === 'geopoint') {
return new GeoPoint(value.latitude, value.longitude);
} else if (type === 'date') {
return new Date(value);
}
console.warn(`Unknown data type received ${type}`);
return value;
}
}

View File

@ -271,6 +271,10 @@ export default class Query {
this._fieldOrders, this._queryOptions);
}
/**
* INTERNALS
*/
/**
* Remove query snapshot listener
* @param listener

View File

@ -28,6 +28,16 @@ function documentReferenceTests({ describe, it, context, firebase }) {
});
});
context('get()', () => {
it('should return DocumentReference field', async () => {
const docRef = firebase.native.firestore().doc('users/6hyiyxQ00JzdWlKFyH3E');
const doc = await docRef.get();
console.log('Doc', doc);
should.equal(doc.exists, true);
await docRef.set(doc.data());
});
});
context('onSnapshot()', () => {
it('calls callback with the initial data and then when value changes', async () => {
const docRef = firebase.native.firestore().doc('document-tests/doc1');