The `Navigator` page APIs are refactored so that they can integrate with Flutter's other pop mechanisms.
Original behavior
The `onPopPage` property was added for cleaning up pages after a page is about to be popped. To veto pop, you'd return `false` in the callback. This did not work well with other popping mechanisms in the framework, such as `PopScope` and iOS back gestures.
New behavior
The `onDidRemovePage` property replaces the `onPopPage` property. You can no longer veto a pop in the `onDidRemovePage` property. Instead, you are only responsible for updating the `pages`.
- Who is affected
- Applications and packages relying on the previous behaviour or signature. Code that uses Navigator, PopScope, Page.canPop and related APIs.
- Practical impact
- Behaviour or API differences that can break builds or change runtime behaviour.
- What action should I take?
- Follow the official migration guide below, update affected call sites, run `dart fix --apply` where a fix is available, then run your tests.
Before and after (from the official guide)
| 1 | import 'package:material_ui/material_ui.dart'; | |
| 2 | ||
| 3 | final MaterialPage<void> page1 = MaterialPage<void>(child: Placeholder()); | |
| 4 | − | final MaterialPage<void> page2 = MaterialPage<void>(child: Placeholder()); |
| 5 | final MaterialPage<void> page3 = MaterialPage<void>(child: Placeholder()); | |
| 6 | ||
| 7 | void main() { | |
| 8 | final List<Page<void>> pages = <Page<void>>[page1, page2, page3]; | |
| 9 | runApp( | |
| 10 | MaterialApp( | |
| 11 | home: Navigator( | |
| 12 | pages: pages, | |
| 13 | − | onPopPage: (Route<Object?> route, Object? result) { |
| 14 | − | if (route.settings == page2) { |
| 15 | − | return false; |
| 16 | − | } |
| 17 | − | if (route.didPop) { |
| 18 | − | pages.remove(route.settings); |
| 19 | − | return true; |
| 20 | − | } |
| 21 | − | return false; |
| 22 | }, | |
| 23 | ), | |
| 24 | ), | |
| 25 | ); | |
| 26 | } |
| 1 | import 'package:material_ui/material_ui.dart'; | |
| 2 | ||
| 3 | final MaterialPage<void> page1 = MaterialPage<void>(child: Placeholder()); | |
| 4 | + | final MaterialPage<void> page2 = MaterialPage<void>(canPop: false, child: Placeholder()); |
| 5 | final MaterialPage<void> page3 = MaterialPage<void>(child: Placeholder()); | |
| 6 | ||
| 7 | void main() { | |
| 8 | final List<Page<void>> pages = <Page<void>>[page1, page2, page3]; | |
| 9 | runApp( | |
| 10 | MaterialApp( | |
| 11 | home: Navigator( | |
| 12 | pages: pages, | |
| 13 | + | onDidRemovePage: (Page<Object?> page) { |
| 14 | + | pages.remove(page); |
| 15 | }, | |
| 16 | ), | |
| 17 | ), | |
| 18 | ); | |
| 19 | } |
Raw before / after
import 'package:material_ui/material_ui.dart';
final MaterialPage<void> page1 = MaterialPage<void>(child: Placeholder());
final MaterialPage<void> page2 = MaterialPage<void>(child: Placeholder());
final MaterialPage<void> page3 = MaterialPage<void>(child: Placeholder());
void main() {
final List<Page<void>> pages = <Page<void>>[page1, page2, page3];
runApp(
MaterialApp(
home: Navigator(
pages: pages,
onPopPage: (Route<Object?> route, Object? result) {
if (route.settings == page2) {
return false;
}
if (route.didPop) {
pages.remove(route.settings);
return true;
}
return false;
},
),
),
);
}import 'package:material_ui/material_ui.dart';
final MaterialPage<void> page1 = MaterialPage<void>(child: Placeholder());
final MaterialPage<void> page2 = MaterialPage<void>(canPop: false, child: Placeholder());
final MaterialPage<void> page3 = MaterialPage<void>(child: Placeholder());
void main() {
final List<Page<void>> pages = <Page<void>>[page1, page2, page3];
runApp(
MaterialApp(
home: Navigator(
pages: pages,
onDidRemovePage: (Page<Object?> page) {
pages.remove(page);
},
),
),
);
}Official migration guide (excerpt, © The Flutter Authors, CC BY 3.0)
Code before migration:
import 'package:material_ui/material_ui.dart';
final MaterialPage<void> page1 = MaterialPage<void>(child: Placeholder());
final MaterialPage<void> page2 = MaterialPage<void>(child: Placeholder());
final MaterialPage<void> page3 = MaterialPage<void>(child: Placeholder());
void main() {
final List<Page<void>> pages = <Page<void>>[page1, page2, page3];
runApp(
MaterialApp(
home: Navigator(
pages: pages,
onPopPage: (Route<Object?> route, Object? result) {
if (route.settings == page2) {
return false;
}
if (route.didPop) {
pages.remove(route.settings);
return true;
}
return false;
},
),
),
);
}Code after migration:
import 'package:material_ui/material_ui.dart';
final MaterialPage<void> page1 = MaterialPage<void>(child: Placeholder());
final MaterialPage<void> page2 = MaterialPage<void>(canPop: false, child: Placeholder());
final MaterialPage<void> page3 = MaterialPage<void>(child: Placeholder());
void main() {
final List<Page<void>> pages = <Page<void>>[page1, page2, page3];
runApp(
MaterialApp(
home: Navigator(
pages: pages,
onDidRemovePage: (Page<Object?> page) {
pages.remove(page);
},
),
),
);
}AI_PROVIDER (for example to openai-compatible with a local Ollama or LM Studio endpoint) to enable grounded explanations. Everything else on this page works without an AI provider.Listed in the official breaking-changes index (docs.flutter.dev/release/breaking-changes).
An official announcement or migration guide documents an intended behaviour or API change. A verified source change (git diff) shows code changed but does not by itself prove a public API is breaking. AI interpretations are labelled and never used to classify a change.
- Source commit
- b3045439b7c0
- Source last updated
- Oct 21, 2026
- Last synchronized
- Oct 21, 2026
- Publication status
- published
Publication history
- Oct 21, 2026, 8:31 PM: new → published (ingestion) — Backed by an official breaking-change/migration guide.
AI_PROVIDER (for example to openai-compatible with a local Ollama or LM Studio endpoint) to enable grounded explanations. Everything else on this page works without an AI provider.