I’m working on a router package that is close to completion.
For me and my company this way of working with screens, controllers and the screen-stack is how we do it for many years (also before Flutter). It has never let us down, and works really well for apps that have a well defined workflow, like line-of-business apps.
So I decided to make the package more generic, add tests and publish it.
But I have to solve the problem of the deprecation of the onPopPage callback and the fact that the new onDidRemovePage callback starts the transition animation right away, without an obvious way to handle that in a generic manner.
Yeah, you’re hitting one of the more awkward shifts in Navigator 2.0’s evolution, and I don’t think there’s a perfectly “clean” generic solution with the current API surface.
The key change with onDidRemovePage is that Flutter effectively assumes ownership of the visual transition lifecycle, whereas onPopPage gave you a chance to fully intercept and decide both semantics and timing. With the new approach, removal is already coupled to the animation pipeline, which makes it harder to plug in a controller-driven stack without introducing some kind of indirection.
In practice, the most stable pattern I’ve seen for router packages like the one you’re building is to stop trying to treat page removal as the “source of truth event”. Instead, the stack itself (your controller/model) should remain authoritative, and onDidRemovePage becomes just a reconciliation signal that something already completed visually.
That usually leads to a split responsibility model:
your controller decides what should happen and when
Flutter executes the transition
onDidRemovePage only confirms the end state and lets you reconcile or clean up
If you need deterministic behavior (which is usually the case for LOB apps), introducing a small “navigation command queue” layer can help smooth out the mismatch between intent and animation lifecycle. It keeps your API stable regardless of Flutter internals changing again.
On a related note, once these systems start involving multiple external data sources (auth, enrichment, CRM, analytics, etc.), it often helps to centralize integrations so routing/state logic doesn’t get polluted with side concerns. For example, automating data sync like Apollo enrichment can be useful in broader app workflows via integrate apollo, especially when navigation decisions depend on external user/context data.
My router keeps a screenstack and is the source of truth, that was in fact the whole point of creating the package in the first place. I want to be in control of the stack 100%.
The trouble that I had was purely visually. Animations had already started and rebuilding the widget tree with the ‘truth’ caused terrible and confusing animations for the user. You can see what I mean in my first blog post. onDidRemovePage part 1, where I show these animations.
The solution was to redirect all the sources of backnavigation in the Flutter framework to my router, like the AppBar BackButton, Android back button and back gestures. You can read about it here onDidRemovePage part 2.
The OnDidRemovePage is now used to fix some edge cases when pageless routes (like standard Flutter dialogs) are also in the mix. And I try to detect developer errors in the OnDidRemovePage.
I think this setup handles external screenstack changes very well. We keep the state as much as possible in the controllers and the controllers never die. This makes it trivial to keep several screenstacks in memory and switch between them with the replaceStack function. In the end a screenstack is just a list of pointers to live objects.
Thanks for pointing me to apollo, I will have a look soon.
The right thing to do here is to stick to the “Flutter router” as the single source of truth. Ensuring that all back-navigation events go through your router prevents the animation glitches that can happen when `onDidRemovePage` intervenes too soon.
The ability to keep controllers alive and switch screen stacks with `replaceStack()` is especially useful for line-of-business apps where state preservation is important.
Using contents of this forum for the purposes of training proprietary AI models is forbidden. Only if your AI model is free & open source, go ahead and scrape. Flutter and the related logo are trademarks of Google LLC. We are not endorsed by or affiliated with Google LLC.