镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

fix(data_connect): send the current result to a new subscriber of a live query - #18833

Draft
Lyokone wants to merge 2 commits into
mainfrom
fix/data-connect-late-subscriber
Draft

Lyokone wants to merge 2 commits into
mainfrom
fix/data-connect-late-subscriber

Conversation

@Lyokone

@Lyokone Lyokone commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Description

When no cache is configured (cacheSettings is null, the default), a subscriber to a query that is already live never got a first result.

FirebaseDataConnect.query() returns the same QueryRef for the same query. When its server stream is already open, subscribe() does not open another one. The server only pushes when the data changes, and the ref kept no result to send, so the new subscriber waited until the next change. A common case: route A is still mounted and subscribed to a query, then route B is pushed and subscribes to the same query. B shows a spinner until the data changes on the server.

Reproduced with a unit test on a fake stream transport: subscriber A got 1 result, B got 0, and one server stream was opened.

Fix. While its server stream is live and no cache is configured, QueryRef keeps the last result sent to its subscribers (from the server stream or from execute()). When a listener attaches while the server stream is live, that result is sent to that listener only, with its original DataSource. subscribe() now returns a Stream.multi that forwards the shared broadcast stream and adds the replay to its own listener, so existing subscribers get nothing extra. It still reports isBroadcast and can be listened to more than once.

  • The last result is cleared when all subscribers cancel and when a new server stream starts. A subscriber that (re)starts the stream waits for that stream's first result and never gets a stale one.
  • The last result is stored with the auth uid that was current when it was published, and is only replayed while that uid is still current. After a user switch the WebSocket transport keeps the server stream open for at least the 1 s reconnect delay, so without this a subscriber in that window got the previous user's result.
  • Errors are not replayed. Every error sent to subscribers also tears the server stream down, so the next subscriber opens a new stream and gets its own result or error.
  • With a cache configured, behaviour is unchanged. subscribe() already reads the cache and sends that result, so nothing is replayed from memory.
Scenario (no cache) before after
B subscribes while A's stream is live B gets nothing B gets the latest result once; A gets no duplicate
Later server push A and B get it A and B get it once
Subscribe after everyone cancelled waits for the new stream unchanged (no stale replay)

Tests

  • Unit tests (test/src/core/ref_resubscribe_test.dart, run by melos run test), in a new QueryRef late subscriber group:
    • a subscriber joining a live query gets the latest result, the existing subscriber gets no duplicate, and both get later pushes once. Fails on main (Expected: ['{"v":2}'] Actual: []), passes with this change.
    • subscribers of a restarted server stream get no stale result, including one that joins while the new stream is still starting. Fails if both _lastResult = null clears are removed.
    • a late subscriber after a mocked auth user switch gets nothing until the new user's result arrives, and that result is replayed to later subscribers. Fails without the uid check.
    • with a cache, a late subscriber gets only the cached result (guard, passes before and after).
  • e2e (example/integration_test/listen_e2e.dart, run by e2e-fdc against the emulator): a listener added to a live listMovies query gets the current result. Run locally on an Android emulator (API 36) against the Data Connect emulator: it times out on main (B never gets a result) and passes with this change, together with the existing listen tests.

Notes

  • Overlap with fix(sql_connect): queries are not single instanced for execute #18500: it adds _streamController = null; at the same spot in _onAllSubscribersCancelled, so expect a small merge conflict. It also covers a case this PR leaves open: two QueryRefs for the same query created before either subscribes.
  • Cache miss (not handled): with a cache configured, a cache miss on a live query still leaves a late subscriber with nothing until the next server push.

Related Issues

None filed. Found in a code audit.

Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • My PR includes unit or integration tests for all changed/updated/fixed behaviors (See Contributor Guide).
  • All existing and new tests are passing.
  • I updated/added relevant documentation (doc comments with ///).
  • The analyzer (melos run analyze) does not report any problems on my PR.
  • I read and followed the Flutter Style Guide.
  • I signed the CLA.
  • I am willing to follow-up on review comments in a timely manner.

Breaking Change

  • No, this is not a breaking change.

Without a cache, subscribing to a query whose server stream was already
open did nothing more, so the new subscriber waited until the server
pushed a change. QueryRef now keeps the last result of the live server
stream and replays it to each new listener only.
@gemini-code-assist

Copy link
Copy Markdown
Contributor
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

After a user switch, the WebSocket transport keeps a query's server
stream open until it has reconnected as the new user, so a subscriber
that joined in that window was sent the previous user's last result.
QueryRef now records the auth uid with the result it keeps for late
subscribers and only replays it when that uid is still current.

The result is also only kept when it can be replayed, i.e. no cache is
configured and the server stream is live.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant