Skip to content

Pass a call on to every delegate the plugin replaced, once each - #20

Merged
tonytonycoder11 merged 2 commits into
mainfrom
hand-notifications-to-each-delegate-once
Oct 30, 2026
Merged

tonytonycoder11 merged 2 commits into
mainfrom
hand-notifications-to-each-delegate-once

Conversation

@tonytonycoder11

Copy link
Copy Markdown
Contributor

Tapping a notification that was not Flutwire's closed an app that also uses Firebase Messaging. It was seen on iOS in a test build that starts Flutwire: the stack in the crash report goes from Flutwire into the notification response handler of Firebase Messaging, and is too deep for the report to show. The same code handles a notification that arrives while the app is open, so that one should loop in the same way, though nobody has seen it.

The plugin takes the notification center twice, at registration in a build where Flutwire has run before and again when Dart starts, and each time it remembered the one delegate it replaced. Firebase Messaging takes the center from a delegate that is not the app's Flutter delegate, keeps it as the delegate to pass every call on to, and does that once. When it does that between the plugin's two takes, the second take replaces Firebase Messaging and remembers it, while Firebase Messaging remembers the plugin. Each hands the call to the other until the stack ends. A first launch of a build has no early take, so the loop needs a second launch, and a build that never starts Flutwire never takes the center at all.

The plugin now keeps every delegate it replaces, newest first, in a small class of its own, and hands a call to the newest one that implements it and that this call has not reached yet. A delegate that hands the call back gets the next one down. In the order above that is the plugin, Firebase Messaging, the plugin again and then the app, each delegate once. Another plugin that keeps the delegate it replaces comes out the same. How the plugin handles its own notification does not change.

CI has no device, so the second commit adds a model of the three delegates, each as its source behaves, and runs the plugin over seven orders in which they can take the center, once as in 1.0.1 and once with the chain. The 1.0.1 version loops in the two orders where Firebase Messaging replaces the plugin between its takes, and the chain reaches every delegate once in all seven. Two simpler fixes failed the same model and were left out: taking the center once per launch loses the plugin's own notification in an app that sets its delegate after the plugins register, and a guard against coming back into the plugin ends the loop but never reaches the app's delegate, so the app shows no banner for a push that arrives while it is open. The model is compiled with swiftc next to the real DelegateChain.swift and runs in the Apple job.

The plugin typechecks against the iOS and macOS frameworks of the Flutter SDK, the model passes, and the new step of the Apple job runs as written. No Dart changed. It was not run on a device, and the model is built from the sources of the three delegates, not from the delegates themselves, so the check that matters is on a device: a second launch of a build that starts Flutwire, then a tap on a push notification. Nothing in the public API changes. Targets 1.0.2, as one Fixed entry and one Internal entry in the changelog.

The plugin takes the notification center at registration, in a build where Flutwire has run before, and again when Dart starts, and each time it remembered the one delegate it replaced. Firebase Messaging takes the center from a delegate that is not the app's Flutter delegate, keeps it as the delegate to pass every call on to, and does that once. When it does that between the plugin's two takes, the plugin's second take replaces Firebase Messaging and remembers it, while Firebase Messaging remembers the plugin, and a tap on a notification that was not Flutwire's went back and forth between the two until the stack ran out. A first launch of a build has no early take, so the loop needs a second launch, and a build that never starts Flutwire never takes the center.

The plugin now keeps every delegate it replaces, newest first, in DelegateChain, and hands a call to the newest one that implements it and that the call has not reached yet. A delegate that hands the call back gets the next one down, so the app's own delegate is still reached and none gets the same call twice.
The Swift half had no check beyond building it, and the notification delegates meet only on a device. The model holds the three that met in the crash, each as its source behaves, and runs the plugin over seven orders in which they can take the center: once as it passed calls on in 1.0.1, which has to loop where Firebase Messaging replaces it between its two takes, and once with the chain, which has to reach every delegate once. It is compiled with swiftc next to the real DelegateChain.swift, so it needs neither a device nor Flutter, and the Apple job runs it. CONTRIBUTING.md says where it is.
@tonytonycoder11 tonytonycoder11 moved this to In progress in Flutwire Oct 30, 2026
@tonytonycoder11
tonytonycoder11 merged commit e5527f3 into main Oct 30, 2026
7 checks passed
@tonytonycoder11
tonytonycoder11 deleted the hand-notifications-to-each-delegate-once branch October 30, 2026 00:52
@tonytonycoder11 tonytonycoder11 moved this from In progress to Shipped in Flutwire Oct 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Shipped

Development

Successfully merging this pull request may close these issues.

1 participant