snapshots method

Stream<DocumentSnapshot<T>> snapshots(
  1. {bool includeMetadataChanges = false,
  2. ListenSource source = ListenSource.defaultSource}
)

Returns a stream of snapshots for the document.

Parameters:

  • includeMetadataChanges: Whether to include metadata changes.
  • source: The source for listening.

Returns: A stream of DocumentSnapshot events.

Example:

docRef.snapshots().listen((snapshot) { /* handle snapshot */ });

Implementation

Stream<DocumentSnapshot<T>> snapshots({
  bool includeMetadataChanges = false,
  ListenSource source = ListenSource.defaultSource,
}) {
  late StreamController<DocumentSnapshot<T>> controller;
  this._rt = 1;
  var lastDocUpdate = null;
  Timer? _pollingTimer;

  bool uselongPolling =
      _oracledb.settings.webExperimentalForceLongPolling ?? true;
  if (_oracledb.settings.webExperimentalForceLongPolling ?? true) {
    uselongPolling = true;
  }

  //uselongPolling = false;

  if (uselongPolling) {
    controller = StreamController<DocumentSnapshot<T>>.broadcast(
      onListen: () async {
        controller.add(await this.get());
      },
      onCancel: () {
        _pollingTimer?.cancel();
      },
    );

    final pollingInterval = _safeLongPollingInterval(_oracledb.settings);

    Future<void> longPolling() async {
      DocumentSnapshot newSnap = await this.get();
      var newVer = null;
      if (newSnap._otherMetadata["ASOF"] != null) {
        newVer = BigInt.from(newSnap._otherMetadata["ASOF"]);
      } else {
        newVer = newSnap._otherMetadata["VERSION"];
      }
      if (lastDocUpdate == null ||
          (newVer != null && lastDocUpdate < newVer)) {
        controller.add(newSnap as DocumentSnapshot<T>);
      }

      lastDocUpdate = newVer;
    }

    _pollingTimer = Timer(pollingInterval, () {
      longPolling();

      _pollingTimer = Timer.periodic(pollingInterval, (Timer timer) {
        longPolling();
      });
    });

    return controller.stream;
  } else {
    bool isCancelled = false;
    bool hasRegisteredRealtimeListener = false;
    FusabaseAuth auth = FusabaseAuth.instanceFor(app: _oracledb.app);
    var email = auth.currentUser?.email ?? "";
    var userLvlPayload = {
      "path": this._path,
      "conditions": [],
      "explicitOrder": [],
      "access_token": email,
    };

    // this query id is used to store in the cache (user level)
    int queryIdInt = createHashFromJson(userLvlPayload);
    String queryId = queryIdInt.toString();

    var socketPayload = {
      "path": this._path,
      "conditions": [],
      "explicitOrder": [],
    };

    // this query id is used to register listener (common for each query)
    int queryIdSocketInt = createHashFromJson(socketPayload);
    String queryIdSocket = queryIdSocketInt.toString();

    // this query id is used to store the notifiers (unique each time)
    String localQueryId = generateRandomHash();

    Future<DocumentSnapshot<T>> firstPoll() async {
      dynamic oldData = await oracledb._snapStore.getData(queryId, oracledb);

      DocumentSnapshot newSnap = await get();

      if (oldData == null) {
        //first snapshot
        await oracledb._snapStore.insertData({
          "queryId": queryId,
          "snap": newSnap,
          "type": "document",
          "path": path,
        });
      } else {
        await oracledb._snapStore.updateData({
          "queryId": queryId,
          "snap": newSnap,
          "type": "document",
          "path": path,
        });
      }
      return newSnap as DocumentSnapshot<T>;
    }

    dynamic payload = {
      "queryId": queryIdSocketInt,
      "status": 1,
      "payload": socketPayload,
    };

    void addToController(dynamic snap) {
      if (!controller.isClosed) {
        controller.add(snap as DocumentSnapshot<T>);
      }
    }

    controller = StreamController<DocumentSnapshot<T>>.broadcast(
      onListen: () async {
        isCancelled = false;
        await _oracledb.preSnapshot();
        if (isCancelled) {
          return;
        }
        _oracledb._queryIdMap[queryIdSocket] = queryId;
        List<String> localQueryIds = _oracledb._localQueryIdMap.putIfAbsent(
          queryId,
          () => [],
        );
        if (!localQueryIds.contains(localQueryId)) {
          localQueryIds.add(localQueryId);
        }
        _oracledb._snapshotControllerMap[localQueryId] = addToController;
        hasRegisteredRealtimeListener = true;
        _oracledb.sendMessageSocket(payload);
        dynamic fsnap = await firstPoll();
        if (!isCancelled && !controller.isClosed) {
          controller.add(fsnap);
        }
      },
      onCancel: () {
        isCancelled = true;
        if (!hasRegisteredRealtimeListener) {
          return;
        }
        hasRegisteredRealtimeListener = false;
        if (_oracledb._snapshotControllerMap.containsKey(localQueryId)) {
          _oracledb._snapshotControllerMap.remove(localQueryId);
        }
        if (_oracledb._localQueryIdMap.containsKey(queryId) &&
            _oracledb._localQueryIdMap[queryId]!.contains(localQueryId)) {
          _oracledb._localQueryIdMap[queryId]?.remove(localQueryId);
        }
        if (!_oracledb._localQueryIdMap.containsKey(queryId) ||
            _oracledb._localQueryIdMap[queryId]!.isEmpty) {
          _oracledb._localQueryIdMap.remove(queryId);
          _oracledb._queryIdMap.remove(queryIdSocket);
          dynamic unsubPayload = {
            "queryId": queryIdSocketInt,
            "status": 0,
            "payload": socketPayload,
          };
          _oracledb.sendMessageSocket(unsubPayload);
        }
      },
    );

    return controller.stream;
  }
}