[Android][IOS][Firestore] add arrayRemove and arrayUnion to FieldValue (#1624)

### Summary

Add methods arrayRemove and arrayUnion to work with arrays in firestore.

Fixes #1389

### Checklist
* [x]  Supports `Android`
* [x]  Supports `iOS`
* [x]  `e2e` tests added or updated in [/tests/e2e/*](/tests/e2e)
* [x]  Updated the documentation in the [docs repo](https://github.com/invertase/react-native-firebase-docs)

https://github.com/invertase/react-native-firebase-docs/pull/134

* [x]  Flow types updated
* [x]  Typescript types updated
This commit is contained in:
Andrzej Lewandowski
2018-10-23 14:40:11 +02:00
committed by Michael Diarmid
parent 991ed4f628
commit 44c7c92acf
7 changed files with 127 additions and 26 deletions

View File

@@ -68,6 +68,10 @@ class FirestoreSerialize {
private static final String TYPE_FIELDVALUE = "fieldvalue";
private static final String TYPE_FIELDVALUE_DELETE = "delete";
private static final String TYPE_FIELDVALUE_TIMESTAMP = "timestamp";
private static final String TYPE_FIELDVALUE_UNION = "union";
private static final String TYPE_FIELDVALUE_REMOVE = "remove";
private static final String TYPE_FIELDVALUE_TYPE = "type";
private static final String TYPE_FIELDVALUE_ELEMENTS = "elements";
// Document Change Types
private static final String CHANGE_ADDED = "added";
@@ -446,7 +450,9 @@ class FirestoreSerialize {
}
if (TYPE_FIELDVALUE.equals(type)) {
String fieldValueType = typeMap.getString(VALUE);
ReadableMap fieldValueMap = typeMap.getMap(VALUE);
String fieldValueType = fieldValueMap.getString(TYPE_FIELDVALUE_TYPE);
if (TYPE_FIELDVALUE_TIMESTAMP.equals(fieldValueType)) {
return FieldValue.serverTimestamp();
@@ -456,6 +462,16 @@ class FirestoreSerialize {
return FieldValue.delete();
}
if (TYPE_FIELDVALUE_UNION.equals(fieldValueType)) {
ReadableArray elements = fieldValueMap.getArray(TYPE_FIELDVALUE_ELEMENTS);
return FieldValue.arrayUnion(elements.toArrayList().toArray());
}
if (TYPE_FIELDVALUE_REMOVE.equals(fieldValueType)) {
ReadableArray elements = fieldValueMap.getArray(TYPE_FIELDVALUE_ELEMENTS);
return FieldValue.arrayRemove(elements.toArrayList().toArray());
}
Log.w(TAG, "Unknown FieldValue type: " + fieldValueType);
return null;
}

View File

@@ -37,6 +37,10 @@ static NSString *const typeTimestamp = @"timestamp";
static NSString *const typeReference = @"reference";
static NSString *const typeDocumentId = @"documentid";
static NSString *const typeFieldValue = @"fieldvalue";
static NSString *const typeFieldValueUnion = @"union";
static NSString *const typeFieldValueRemove = @"remove";
static NSString *const typeFieldValueType = @"type";
static NSString *const typeFieldValueElements = @"elements";
- (id)initWithPath:(RCTEventEmitter *)emitter
appDisplayName:(NSString *)appDisplayName
@@ -408,8 +412,9 @@ static NSString *const typeFieldValue = @"fieldvalue";
}
if ([type isEqualToString:typeFieldValue]) {
NSString *string = (NSString *) value;
NSDictionary *fieldValueMap = (NSDictionary *) value;
NSString *string = (NSString *) fieldValueMap[typeFieldValueType];
if ([string isEqualToString:typeDelete]) {
return [FIRFieldValue fieldValueForDelete];
}
@@ -417,6 +422,16 @@ static NSString *const typeFieldValue = @"fieldvalue";
if ([string isEqualToString:typeTimestamp]) {
return [FIRFieldValue fieldValueForServerTimestamp];
}
if ([string isEqualToString:typeFieldValueUnion]) {
NSDictionary *elements = (NSDictionary *) value[typeFieldValueElements];
return [FIRFieldValue fieldValueForArrayUnion:elements];
}
if ([string isEqualToString:typeFieldValueRemove]) {
NSDictionary *elements = (NSDictionary *) value[typeFieldValueElements];
return [FIRFieldValue fieldValueForArrayRemove:elements];
}
DLog(@"RNFirebaseFirestore: Unsupported field-value sent to parseJSTypeMap - value is %@",
NSStringFromClass([value class]));

6
src/index.d.ts vendored
View File

@@ -2404,10 +2404,16 @@ declare module 'react-native-firebase' {
constructor(...segments: string[]);
}
type AnyJs = null | undefined | boolean | number | string | object;
class FieldValue {
static delete(): FieldValue;
static serverTimestamp(): FieldValue;
static arrayUnion(...elements: AnyJs[]): FieldValue;
static arrayRemove(...elements: AnyJs[]): FieldValue;
}
class GeoPoint {

View File

@@ -2,16 +2,45 @@
* @flow
* FieldValue representation wrapper
*/
import AnyJs from './utils/any';
// TODO: Salakar: Refactor in v6
export default class FieldValue {
_type: string;
_elements: AnyJs[] | any;
constructor(type: string, elements?: AnyJs[]) {
this._type = type;
this._elements = elements;
}
get type(): string {
return this._type;
}
get elements(): AnyJs[] {
return this._elements;
}
static delete(): FieldValue {
return DELETE_FIELD_VALUE;
return new FieldValue(TypeFieldValueDelete);
}
static serverTimestamp(): FieldValue {
return SERVER_TIMESTAMP_FIELD_VALUE;
return new FieldValue(TypeFieldValueTimestamp);
}
static arrayUnion(...elements: AnyJs[]) {
return new FieldValue(TypeFieldValueUnion, elements);
}
static arrayRemove(...elements: AnyJs[]) {
return new FieldValue(TypeFieldValueRemove, elements);
}
}
export const DELETE_FIELD_VALUE = new FieldValue();
export const SERVER_TIMESTAMP_FIELD_VALUE = new FieldValue();
export const TypeFieldValueDelete = 'delete';
export const TypeFieldValueRemove = 'remove';
export const TypeFieldValueUnion = 'union';
export const TypeFieldValueTimestamp = 'timestamp';

View File

@@ -0,0 +1,4 @@
/**
* @url https://github.com/firebase/firebase-js-sdk/blob/master/packages/firestore/src/util/misc.ts#L26
*/
export type AnyJs = null | undefined | boolean | number | string | object;

View File

@@ -5,10 +5,7 @@
import DocumentReference from '../DocumentReference';
import Blob from '../Blob';
import { DOCUMENT_ID } from '../FieldPath';
import {
DELETE_FIELD_VALUE,
SERVER_TIMESTAMP_FIELD_VALUE,
} from '../FieldValue';
import FieldValue from '../FieldValue';
import GeoPoint from '../GeoPoint';
import Path from '../Path';
import { typeOf } from '../../../utils';
@@ -72,20 +69,6 @@ export const buildTypeMap = (value: any): NativeTypeMap | null => {
};
}
if (value === DELETE_FIELD_VALUE) {
return {
type: 'fieldvalue',
value: 'delete',
};
}
if (value === SERVER_TIMESTAMP_FIELD_VALUE) {
return {
type: 'fieldvalue',
value: 'timestamp',
};
}
if (value === DOCUMENT_ID) {
return {
type: 'documentid',
@@ -139,6 +122,17 @@ export const buildTypeMap = (value: any): NativeTypeMap | null => {
};
}
// TODO: Salakar: Refactor in v6 - add internal `type` flag
if (value instanceof FieldValue) {
return {
type: 'fieldvalue',
value: {
elements: value.elements,
type: value.type,
},
};
}
return {
type: 'object',
value: buildNativeMap(value),

View File

@@ -7,7 +7,7 @@ const {
describe('firestore()', () => {
describe('FieldValue', () => {
before(async () => {
beforeEach(async () => {
await resetTestCollectionDoc(DOC_2_PATH, DOC_2);
});
@@ -46,5 +46,42 @@ describe('firestore()', () => {
);
});
});
describe('arrayUnion()', () => {
it('should add new values to array field', async () => {
const { data } = await testCollectionDoc(DOC_2_PATH).get();
should.equal(data().elements, undefined);
await testCollectionDoc(DOC_2_PATH).update({
elements: firebase.firestore.FieldValue.arrayUnion('element 1'),
elements2: firebase.firestore.FieldValue.arrayUnion('element 2'),
});
const { data: dataAfterUpdate } = await testCollectionDoc(
DOC_2_PATH
).get();
dataAfterUpdate().elements.should.containDeep(['element 1']);
dataAfterUpdate().elements2.should.containDeep(['element 2']);
});
});
describe('arrayRemove()', () => {
it('should remove value from array', async () => {
await testCollectionDoc(DOC_2_PATH).set({
elements: ['element 1', 'element 2'],
});
const { data } = await testCollectionDoc(DOC_2_PATH).get();
data().elements.should.containDeep(['element 1', 'element 2']);
await testCollectionDoc(DOC_2_PATH).update({
elements: firebase.firestore.FieldValue.arrayRemove('element 2'),
});
const { data: dataAfterUpdate } = await testCollectionDoc(
DOC_2_PATH
).get();
dataAfterUpdate().elements.should.not.containDeep(['element 2']);
});
});
});
});