onQuery function

Stream<List<QueryDocument>> onQuery(
  1. String collectionPath, {
  2. List<Where> where = const [],
  3. List<OrderBy> orderBy = const [],
  4. int? limit,
  5. int? limitToLast,
  6. List<Object?>? startAt,
  7. List<Object?>? startAfter,
  8. List<Object?>? endAt,
  9. List<Object?>? endBefore,
  10. bool collectionGroup = false,
})

Watches a query, emitting the whole result each time it changes.

The same spec as queryCollection, parsed by the same code natively — a second parser would be free to disagree about what a query means.

Implementation

Stream<List<QueryDocument>> onQuery(
  String collectionPath, {
  List<Where> where = const [],
  List<OrderBy> orderBy = const [],
  int? limit,
  int? limitToLast,
  List<Object?>? startAt,
  List<Object?>? startAfter,
  List<Object?>? endAt,
  List<Object?>? endBefore,

  /// Searches every collection with this id, at any depth, rather than one
  /// collection at a path.
  bool collectionGroup = false,
}) {
  final encoded = _encodeSpec(
    _querySpec(
      where: where,
      orderBy: orderBy,
      limit: limit,
      limitToLast: limitToLast,
      startAt: startAt,
      startAfter: startAfter,
      endAt: endAt,
      endBefore: endBefore,
      collectionGroup: collectionGroup,
    ),
  );

  late StreamController<List<QueryDocument>> controller;
  late RawReceivePort receive;
  var listenerId = 0;

  void stop() {
    if (listenerId > 0) fdbFsUnlisten(listenerId);
    receive.close();
  }

  controller = StreamController<List<QueryDocument>>(
    onCancel: stop,
    onListen: () {
      receive = RawReceivePort();
      receive.handler = (Object? message) {
        final bytes = message! as Uint8List;
        final seq = ByteData.sublistView(bytes).getInt64(8, Endian.host);
        if (seq < 0) {
          // The payload carries the reason rather than just the fact: a
          // listener that stops silently is nearly always a rules problem.
          final reason = decodeSnapshotValue(bytes);
          controller.addError(
            StateError(
              'firestore query listener canceled: ${reason ?? "no reason"}',
            ),
          );
          return;
        }
        controller.add(_decodeQueryResult(bytes));
      };

      final p = collectionPath.toNativeUtf8();
      final buf = calloc<Uint8>(encoded.isEmpty ? 1 : encoded.length);
      if (encoded.isNotEmpty) {
        buf.asTypedList(encoded.length).setAll(0, encoded);
      }
      listenerId = fdbFsQueryListen(
        p.cast(),
        buf,
        encoded.length,
        receive.sendPort.nativePort,
      );
      calloc
        ..free(p)
        ..free(buf);
      if (listenerId < 0) {
        controller.addError(
          listenerId == -3 || listenerId == -4
              ? ArgumentError('this query cannot be watched as expressed')
              : StateError('watch $collectionPath failed ($listenerId)'),
        );
        stop();
      }
    },
  );
  return controller.stream;
}