Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions packages/cloud_firestore/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
## 0.12.10

* Added `FieldPath` class and `FieldPath.documentId` to refer to the document id in queries.
* Added assertions and exceptions that help you building correct queries.

## 0.12.9+8

* Updated README instructions for contributing for consistency with other Flutterfire plugins.

## 0.12.9+7

* Remove AndroidX warning.
Expand Down
12 changes: 10 additions & 2 deletions packages/cloud_firestore/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ A Flutter plugin to use the [Cloud Firestore API](https://firebase.google.com/do

For Flutter plugins for other Firebase products, see [README.md](https://github.com/FirebaseExtended/flutterfire/blob/master/README.md).

*Note*: This plugin is still under development, and some APIs might not be available yet. [Feedback](https://github.com/FirebaseExtended/flutterfire/issues) and [Pull Requests](https://github.com/FirebaseExtended/flutterfire/pulls) are most welcome!

## Setup

To use this plugin:
Expand Down Expand Up @@ -102,3 +100,13 @@ Firestore.instance.runTransaction((Transaction tx) async {
## Getting Started

See the `example` directory for a complete sample app using Cloud Firestore.

## Issues and feedback

Please file Flutterfire specific issues, bugs, or feature requests in our [issue tracker](https://github.com/FirebaseExtended/flutterfire/issues/new).

Plugin issues that are not specific to Flutterfire can be filed in the [Flutter issue tracker](https://github.com/flutter/flutter/issues/new).

To contribute a change to this plugin,
please review our [contribution guide](https://github.com/FirebaseExtended/flutterfire/blob/master/CONTRIBUTING.md),
and send a [pull request](https://github.com/FirebaseExtended/flutterfire/pulls).
Original file line number Diff line number Diff line change
Expand Up @@ -129,16 +129,32 @@ private Object[] getDocumentValues(
List<Object> data = new ArrayList<>();
if (orderBy != null) {
for (List<Object> order : orderBy) {
String orderByFieldName = (String) order.get(0);
if (orderByFieldName.contains(".")) {
String[] fieldNameParts = orderByFieldName.split("\\.");
Map<String, Object> current = (Map<String, Object>) documentData.get(fieldNameParts[0]);
for (int i = 1; i < fieldNameParts.length - 1; i++) {
current = (Map<String, Object>) current.get(fieldNameParts[i]);
final Object field = order.get(0);

if (field instanceof FieldPath) {
if (field == FieldPath.documentId()) {
// This is also checked by an assertion on the Dart side.
throw new IllegalArgumentException(
"You cannot order by the document id when using"
+ "{start/end}{At/After/Before}Document as the library will order by the document"
+ " id implicitly in order to to add other fields to the order clause.");
} else {
// Unsupported type.
}
} else if (field instanceof String) {
String orderByFieldName = (String) field;
if (orderByFieldName.contains(".")) {
String[] fieldNameParts = orderByFieldName.split("\\.");
Map<String, Object> current = (Map<String, Object>) documentData.get(fieldNameParts[0]);
for (int i = 1; i < fieldNameParts.length - 1; i++) {
current = (Map<String, Object>) current.get(fieldNameParts[i]);
}
data.add(current.get(fieldNameParts[fieldNameParts.length - 1]));
} else {
data.add(documentData.get(orderByFieldName));
}
data.add(current.get(fieldNameParts[fieldNameParts.length - 1]));
} else {
data.add(documentData.get(orderByFieldName));
// Invalid type.
}
}
}
Expand Down Expand Up @@ -213,21 +229,67 @@ private Query getQuery(Map<String, Object> arguments) {
@SuppressWarnings("unchecked")
List<List<Object>> whereConditions = (List<List<Object>>) parameters.get("where");
for (List<Object> condition : whereConditions) {
String fieldName = (String) condition.get(0);
String fieldName = null;
FieldPath fieldPath = null;
final Object field = condition.get(0);
if (field instanceof String) {
fieldName = (String) field;
} else if (field instanceof FieldPath) {
fieldPath = (FieldPath) field;
} else {
// Invalid type.
}

String operator = (String) condition.get(1);
Object value = condition.get(2);
if ("==".equals(operator)) {
query = query.whereEqualTo(fieldName, value);
if (fieldName != null) {
query = query.whereEqualTo(fieldName, value);
} else if (fieldPath != null) {
query = query.whereEqualTo(fieldPath, value);
} else {
// Invalid type.
}
} else if ("<".equals(operator)) {
query = query.whereLessThan(fieldName, value);
if (fieldName != null) {
query = query.whereLessThan(fieldName, value);
} else if (fieldPath != null) {
query = query.whereLessThan(fieldPath, value);
} else {
// Invalid type.
}
} else if ("<=".equals(operator)) {
query = query.whereLessThanOrEqualTo(fieldName, value);
if (fieldName != null) {
query = query.whereLessThanOrEqualTo(fieldName, value);
} else if (fieldPath != null) {
query = query.whereLessThanOrEqualTo(fieldPath, value);
} else {
// Invalid type.
}
} else if (">".equals(operator)) {
query = query.whereGreaterThan(fieldName, value);
if (fieldName != null) {
query = query.whereGreaterThan(fieldName, value);
} else if (fieldPath != null) {
query = query.whereGreaterThan(fieldPath, value);
} else {
// Invalid type.
}
} else if (">=".equals(operator)) {
query = query.whereGreaterThanOrEqualTo(fieldName, value);
if (fieldName != null) {
query = query.whereGreaterThanOrEqualTo(fieldName, value);
} else if (fieldPath != null) {
query = query.whereGreaterThanOrEqualTo(fieldPath, value);
} else {
// Invalid type.
}
} else if ("array-contains".equals(operator)) {
query = query.whereArrayContains(fieldName, value);
if (fieldName != null) {
query = query.whereArrayContains(fieldName, value);
} else if (fieldPath != null) {
query = query.whereArrayContains(fieldPath, value);
} else {
// Invalid type.
}
} else {
// Invalid operator.
}
Expand All @@ -239,11 +301,28 @@ private Query getQuery(Map<String, Object> arguments) {
List<List<Object>> orderBy = (List<List<Object>>) parameters.get("orderBy");
if (orderBy == null) return query;
for (List<Object> order : orderBy) {
String orderByFieldName = (String) order.get(0);
String fieldName = null;
FieldPath fieldPath = null;
final Object field = order.get(0);
if (field instanceof String) {
fieldName = (String) field;
} else if (field instanceof FieldPath) {
fieldPath = (FieldPath) field;
} else {
// Invalid type.
}

boolean descending = (boolean) order.get(1);
Query.Direction direction =
descending ? Query.Direction.DESCENDING : Query.Direction.ASCENDING;
query = query.orderBy(orderByFieldName, direction);

if (fieldName != null) {
query = query.orderBy(fieldName, direction);
} else if (fieldPath != null) {
query = query.orderBy(fieldPath, direction);
} else {
// Invalid type.
}
}
@SuppressWarnings("unchecked")
Map<String, Object> startAtDocument = (Map<String, Object>) parameters.get("startAtDocument");
Expand All @@ -259,6 +338,11 @@ private Query getQuery(Map<String, Object> arguments) {
|| startAfterDocument != null
|| endAtDocument != null
|| endBeforeDocument != null) {
if (orderBy.isEmpty()) {
throw new IllegalStateException(
"You need to order by at least one field when using "
+ "{start/end}{At/After/Before}Document as you need some value to e.g. start after.");
}
boolean descending = (boolean) orderBy.get(orderBy.size() - 1).get(1);
Query.Direction direction =
descending ? Query.Direction.DESCENDING : Query.Direction.ASCENDING;
Expand Down Expand Up @@ -859,6 +943,7 @@ final class FirestoreMessageCodec extends StandardMessageCodec {
private static final byte TIMESTAMP = (byte) 136;
private static final byte INCREMENT_DOUBLE = (byte) 137;
private static final byte INCREMENT_INTEGER = (byte) 138;
private static final byte DOCUMENT_ID = (byte) 139;

@Override
protected void writeValue(ByteArrayOutputStream stream, Object value) {
Expand Down Expand Up @@ -922,6 +1007,8 @@ protected Object readValueOfType(byte type, ByteBuffer buffer) {
case INCREMENT_DOUBLE:
final Number doubleIncrementValue = (Number) readValue(buffer);
return FieldValue.increment(doubleIncrementValue.doubleValue());
case DOCUMENT_ID:
return FieldPath.documentId();
default:
return super.readValueOfType(type, buffer);
}
Expand Down
34 changes: 34 additions & 0 deletions packages/cloud_firestore/example/test_driver/cloud_firestore.dart
Original file line number Diff line number Diff line change
Expand Up @@ -294,5 +294,39 @@ void main() {
await doc1.delete();
await doc2.delete();
});

test('FieldPath.documentId', () async {
// Populate the database with two test documents.
final CollectionReference messages = firestore.collection('messages');

// Use document ID as a unique identifier to ensure that we don't
// collide with other tests running against this database.
final DocumentReference doc = messages.document();
final String documentId = doc.documentID;

await doc.setData(<String, dynamic>{
'message': 'testing field path',
'created_at': FieldValue.serverTimestamp(),
});

// This tests the native implementations of the where and
// orderBy methods handling FieldPath.documentId.
// There is also an error thrown when ordering by document id
// natively, however, that is also covered by assertion
// on the Dart side, which is tested with a unit test.
final QuerySnapshot querySnapshot = await messages
.orderBy(FieldPath.documentId)
.where(FieldPath.documentId, isEqualTo: documentId)
.getDocuments();

await doc.delete();

final List<DocumentSnapshot> results = querySnapshot.documents;
final DocumentSnapshot result = results[0];

expect(results.length, 1);
expect(result.data['message'], 'testing field path');
expect(result.documentID, documentId);
});
});
}
Loading