Skip to content

Instantly share code, notes, and snippets.

@Jonas-Sander
Created November 23, 2020 22:47
Show Gist options
  • Save Jonas-Sander/4ad27f11aaeeeccb1ed0fef39777d6b6 to your computer and use it in GitHub Desktop.
Save Jonas-Sander/4ad27f11aaeeeccb1ed0fef39777d6b6 to your computer and use it in GitHub Desktop.
Fixed googleapis Firestore Query
import 'dart:convert';
// ignore: import_of_legacy_library_into_null_safe
import 'package:googleapis/firestore/v1.dart';
// ignore: import_of_legacy_library_into_null_safe
import 'package:http/http.dart' as http;
extension FirestoreRunQueryFixedExtension
on ProjectsDatabasesDocumentsResourceApi {
/// Runs a query.
///
/// [request] - The metadata request object.
///
/// Request parameters:
///
/// [parent] - The parent resource name. In the format:
/// `projects/{project_id}/databases/{database_id}/documents` or
/// `projects/{project_id}/databases/{database_id}/documents/{document_path}`.
/// For example:
/// `projects/my-project/databases/my-database/documents` or
/// `projects/my-project/databases/my-database/documents/chatrooms/my-chatroom`
/// Value must have pattern
/// "^projects/[^/]+/databases/[^/]+/documents/[^/]+/.+$".
///
/// [$fields] - Selector specifying which fields to include in a partial
/// response.
///
/// Completes with a [RunQueryResponse].
///
/// Completes with a [commons.ApiRequestError] if the API endpoint returned an
/// error.
///
/// If the used [http.Client] completes with an error when making a REST call,
/// this method will complete with the same error.
Future<List<Document>> runQueryFixed(RunQueryRequest request,
{required http.Client client, required String? parent}) async {
final urlParentAddition = parent != null ? '/$parent' : '';
final url =
'https://firestore.googleapis.com/v1/projects/sharezone-debug/databases/(default)/documents$urlParentAddition:runQuery';
final body = json.encode(request.toJson());
final response = await client.post(url, body: body);
final resBody = response.body;
final List<dynamic> decoded = json.decode(resBody) as List;
if (decoded[0]['error'] != null) {
throw Exception('Firestore Error: $resBody!');
}
final docs = [
for (final docJson in decoded)
if (docJson['document'] != null)
Document.fromJson(docJson['document'] as Map)
];
return docs;
}
}
Future<List<Document>> runQuery({
required String fieldPath,
required String operation,
required dynamic value,
required String collectionId,
required String? parentPath,
required ProjectsDatabasesDocumentsResourceApi api,
required http.Client client,
int? limit,
}) async {
final chatFieldReference = FieldReference()..fieldPath = fieldPath;
final fstoreValue = '$value'.toFirestoreValue();
final fieldFilter = FieldFilter()
..field = chatFieldReference
..op = operation
..value = fstoreValue;
final filter = Filter()..fieldFilter = fieldFilter;
final structuredQuery = StructuredQuery()
..where = filter
..from = [CollectionSelector()..collectionId = collectionId];
if (limit != null) {
structuredQuery.limit = limit;
}
final runQueryRequest = RunQueryRequest()..structuredQuery = structuredQuery;
final documents = await api.runQueryFixed(runQueryRequest,
client: client, parent: parentPath);
return documents;
}
Future<List<Document>> runMultiConditionQuery({
required List<Condition> conditions,
required String collectionId,
required String? parentPath,
required ProjectsDatabasesDocumentsResourceApi api,
required http.Client client,
}) async {
final filters = conditions.map((condition) {
final chatFieldReference = FieldReference()
..fieldPath = condition.fieldPath;
final fstoreValue = '${condition.value}'.toFirestoreValue();
final fieldFilter = FieldFilter()
..field = chatFieldReference
..op = condition.operation
..value = fstoreValue;
return Filter()..fieldFilter = fieldFilter;
}).toList();
final compositeFilter = CompositeFilter()
..filters = filters
..op = 'AND';
final filter = Filter()..compositeFilter = compositeFilter;
final structuredQuery = StructuredQuery()
..where = filter
..from = [CollectionSelector()..collectionId = collectionId];
final runQueryRequest = RunQueryRequest()..structuredQuery = structuredQuery;
final documents = await api.runQueryFixed(runQueryRequest,
client: client, parent: parentPath);
return documents;
}
class Condition {
final String fieldPath;
final String operation;
final dynamic value;
Condition({
required this.fieldPath,
required this.operation,
required this.value,
});
}
extension MapToFirestoreMap on Map<String, dynamic> {
Map<String, Value> toFirestoreMap() {
return map((key, value) {
return MapEntry(key, _toFirestoreValue(value));
});
}
}
extension ToFirestoreValueList<T> on Iterable<T> {
Value toFirestoreValue() {
final arrVal = ArrayValue()
..values = map<Value>((e) => (e as dynamic).toFirestoreValue() as Value)
.toList();
return Value()..arrayValue = arrVal;
}
}
Value _toFirestoreValue(dynamic val) {
if (val is String) {
return Value()..stringValue = val;
}
if (val is int) {
return Value()..integerValue = val.toString();
}
if (val is bool) {
return Value()..booleanValue = val;
}
if (val is DateTime) {
return Value()..timestampValue = val.toUtcIso8601String();
}
if (val is double) {
return Value()..doubleValue = val;
}
if (val is List) {
final arrVal = ArrayValue()
..values = val.map<Value>(_toFirestoreValue).toList();
return Value()..arrayValue = arrVal;
}
if (val is Map<String, dynamic>) {
final mapVals = val.toFirestoreMap();
final mapVal = MapValue()..fields = mapVals;
return Value()..mapValue = mapVal;
}
if (val is Set) {
return Value()..arrayValue = _toFirestoreValue(val.toList()).arrayValue;
}
throw ArgumentError(
'Cant convert ${val.runtimeType} to a Firestore Value.',
);
}
extension ToFirestoreValue on Object {
Value toFirestoreValue() => _toFirestoreValue(this);
}
extension FirestoreMapToMap on Map<String, Value> {
Map<String, dynamic> toPrimitives() {
return map((key, value) => MapEntry(key, value.toPrimitive()));
}
}
extension ValueToPrimitive on Value {
dynamic toPrimitive() {
if (arrayValue != null) {
return [
for (final val in arrayValue.values) val.toPrimitive(),
];
}
if (booleanValue != null) {
return booleanValue;
}
if (bytesValue != null) {
return bytesValueAsBytes;
}
if (doubleValue != null) {
return doubleValue;
}
if (geoPointValue != null) {
return geoPointValue;
}
if (integerValue != null) {
return int.parse(integerValue);
}
if (mapValue != null) {
return mapValue.fields
.map((key, value) => MapEntry(key, value.toPrimitive()));
}
if (nullValue != null) {
return null;
}
if (referenceValue != null) {
return referenceValue;
}
if (stringValue != null) {
return stringValue;
}
if (timestampValue != null) {
return DateTime.parse(timestampValue);
}
throw UnimplementedError('Cant convert to primtive ${toJson()}');
}
}
extension DateTimeToUtcIso8601StringExtension on DateTime {
String toUtcIso8601String() {
if (!isUtc) {
return toUtc().toIso8601String();
}
return toIso8601String();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment