fix(firebase_storage): clean up Apple task listeners - #18658
Conversation
|
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
Using Gemini Code AssistThe 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
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 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. |
Extract the event dispatcher for deterministic native XCTest coverage of cancellation, relistening, terminal cleanup, main-queue delivery, and sink release. Add SwiftPM test targets and a standalone test runner, apply repository CI formatting, and add the required contributor entry.
Flutter delivers detachFromEngine to published plugin instances. Register the instance with publish so handler invalidation actually runs during engine deallocation.
|
Hi @Wackymax, thanks for the PR. Will review this and provide feedback. |
|
Hi @Wackymax, a couple of questions before we go further. 1. The crash this cites is not the path #8919 / #11001 are This PR says that path is out of scope, then still hangs the work on those issues. Can you clarify what user-visible failure this is meant to fix, if not #8919 / #11001? If the intent is only “clean up when Flutter does detach,” that’s fine, but we should drop those issue links (or mark them related-but-unfixed) so we don’t claim a shutdown crash this hook cannot reach. 2. The useful Storage gap looks much smaller than the dispatcher. Auth and Firestore already: retain stream handlers, That does not obviously need a new |
|
Hi @SelaseKay, thanks for taking the time to look at this. You are right about the engine lifecycle. I went back and tested it with a small Flutter app without Firebase, and I also had a look at the cancellation case with the engine still running. With a real paused upload, cancelling the listener before the queued callback runs still calls the old sink. The same happens if I cancel and start listening again straight away. Both tests fail with the current upstream code and pass with this change. That confirms the stale callback, although I haven't reproduced a user-visible problem from that case alone. The difference I found with Firestore is that its native SDK mutes the listener when it is removed. Storage's removeObserver removes it from the dictionary, but callbacks that are already queued can still run. I've simplified the implementation as well. The dispatcher is gone and each listener now just holds a sink that gets cleared before removing the observers. Cleanup uses the existing I removed the Ruby harness and moved the native tests into the example's |
|
Hi @SelaseKay @russellwheatley, sorry I missed wiring these into CI. You are right, the green iOS/macOS jobs were only running the Dart tests. I've added the native tests to the Storage iOS job in 9abe498. It runs the existing script with the Storage emulator, so all 9 RunnerTests cases now run through The workflow checks pass locally and I've checked that failures are passed through correctly. The new CI run is waiting for maintainer approval. Thanks for catching this. |
Description
Firebase Storage can invoke an old Flutter event sink after its listener is cancelled: removing an observer does not retract Firebase callbacks already queued for delivery. Storage also declares a stream-handler registry without populating it, does not publish its iOS plugin for detach callbacks, and does not participate in Firebase Core reinitialization cleanup.
This change gives each listen an optional sink that is cleared before observer removal. A queued callback from that listen then stops without reaching either the cancelled sink or a later subscription. Sink access and delivery are serialized on the main queue; native callbacks already on main are delivered without an extra queue hop. Terminal delivery clears the listener and observers before sending its final event.
The plugin retains handlers, uses their standard
onCancelcallback for cleanup, publishes on iOS, and registers with Firebase Core so repeated initialization clears old channels and task-handle maps. Cleanup does not explicitly cancel native transfers and preserves emulator configuration. Storage's library version constant is included in the existing version-generation script.The generic
TaskEventDispatcher, generation counters, helper-only tests, and Ruby project-mutating harness have been removed. Real Firebase/Flutter tests now live in a checked-inRunnerTeststarget under the example's normalRunnerscheme. The test bundle uses the host app's plugin code rather than linking duplicate static package copies. The example integration setup also avoids redundant default-app initialization and uses initialized native options for secondary apps.This does not fix the retained-engine
destroyContext()/ shell-reset crash. That path does not invokedetachFromEngine. It is tracked separately in flutter/flutter#126671, with a tested no-Firebase reproduction and disposal control. The demonstrated failure addressed here is stale native callback delivery and listener cleanup; no end-user UI failure or fix for those shutdown crashes is claimed.Validation
RunnerTests: 9 XCTest cases passed on an iOS 26.5 simulator with Xcode 27. Tests use real Firebase upload tasks and real Flutter engines, covering cancellation with a queued callback, relistening, background callback delivery/invalidation, immediate sink release, terminal success/failure, engine disposal, and repeated Firebase Core initialization followed by a fresh successful upload.dart run melos exec --scope='firebase_storage*' -c 1 -- dart analyze . --fatal-infos. Strict analysis of the updated version-generation script also passed.git diff --checkpassed. Full native plugin/example compilation is exercised by the native and emulator suites; existing unrelated native compiler warnings remain.melos bootstrapcompleted. Flutter-generated project migrations are excluded from the patch; only the checked-in test target/scheme changes are retained.The earlier full-workspace analyzer run encountered missing Firebase AI example configuration and an unrelated Firebase Performance Web telemetry network failure. Affected Storage packages and the updated script pass strict analysis.
Checklist
///).melos run analyze) does not report any problems on my PR. (Scoped strict analysis passes; unrelated full-workspace issues are detailed above.)Breaking Change
Does your PR require plugin users to manually update their apps to accommodate your change?