Skip to content
Boring Doc
DeprecatedMedium impactOfficial migration guidesince 3.16.0landed 3.14.0-7.0.preandroid

Deprecated just-in-time navigation pop APIs for Android Predictive Back

Framework · Official source

To support Android 14's Predictive Back feature, a set of ahead-of-time APIs have replaced just-in-time navigation APIs, like `WillPopScope` and `Navigator.willPop`.

Who is affected
Code that still calls the deprecated API; it keeps working until removal but produces analyzer warnings. Code that uses WillPopScope, Navigator.willPop, PopScope and related APIs.
Practical impact
Warnings now; removal in a future release. Plan the migration but no immediate break.
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)

dart+2−4
Before
1−WillPopScope(
2− onWillPop: () async {
3− return _myCondition;
4− },
5 child: ...
6 ),
After
1+PopScope(
2+ canPop: _myCondition,
 
 
3 child: ...
4 ),
Raw before / after
WillPopScope(
  onWillPop: () async {
    return _myCondition;
  },
  child: ...
),
PopScope(
  canPop: _myCondition,
  child: ...
),
Official migration guide (excerpt, © The Flutter Authors, CC BY 3.0)

Migrating from WillPopScope to PopScope

The direct replacement of the WillPopScope widget is the PopScope widget. In many cases, logic that was being run at the time of the back gesture in onWillPop can be done at build time and set to canPop.

Code before migration:

WillPopScope(
  onWillPop: () async {
    return _myCondition;
  },
  child: ...
),

Code after migration:

PopScope(
  canPop: _myCondition,
  child: ...
),

For cases where it's necessary to be notified that a pop was attempted, the onPopInvokedWithResult method can be used in a similar way to onWillPop. Keep in mind that while onWillPop was called before the pop was handled and had the ability to cancel it, onPopInvokedWithResult is called after the pop is finished being handled.

Code before migration:

WillPopScope(
  onWillPop: () async {
    _myHandleOnPopMethod();
    return true;
  },
  child: ...
),

Code after migration:

PopScope(
  canPop: true,
  onPopInvokedWithResult: (bool didPop, Object? result) {
    _myHandleOnPopMethod();
  },
  child: ...
),

Migrating from WillPopScope to NavigatorPopHandler for nested Navigators

A very common use case of WillPopScope was to properly handle back gestures when using nested Navigator widgets. It's possible to do this using PopScope as well, but there is now a wrapper widget that makes this even easier: NavigatorPopHandler.

Code before migration:

WillPopScope(
  onWillPop: () async => !(await _nestedNavigatorKey.currentState!.maybePop()),
  child: Navigator(
    key: _nestedNavigatorKey,
    …
  ),
)

Code after migration:

NavigatorPopHandler(
  onPop: () => _nestedNavigatorKey.currentState!.pop(),
  child: Navigator(
    key: _nestedNavigatorKey,
    …
  ),
)

Migrating from Form.onWillPop to Form.canPop and Form.onPopInvokedWithResult

Previously, Form used a WillPopScope instance under the hood and exposed its onWillPop method. This has been replaced with a PopScope that exposes its canPop and onPopInvokedWithResult methods. Migrating is identical to migrating from WillPopScope to PopScope, detailed above.

Migrating from Route.willPop to Route.popDisposition

Route's willPop method returned a Future<RoutePopDisposition> to accommodate the fact that pops could be canceled. Now that that's no longer true, this logic has been simplified to a synchronous getter.

Code before migration:

if (await myRoute.willPop() == RoutePopDisposition.doNotPop) {
  ...
}

Code after migration:

if (myRoute.popDisposition == RoutePopDisposition.doNotPop) {
  ...
}

Migrating from ModalRoute.add/removeScopedWillPopCallback to ModalRoute.(un)registerPopEntry

Internally, ModalRoute kept track of the existence of WillPopScopes in its widget subtree by registering them with addScopedWillPopCallback and removeScopedWillPopCallback. Since PopScope replaces WillPopScope, these methods have been replaced by registerPopEntry and unregisterPopEntry, respectively.

PopEntry is implemented by PopScope in order to expose only the minimal information necessary to ModalRoute. Anyone writing their own PopScope should implement PopEntry and register and unregister their widget with its enclosing ModalRoute.

Code before migration:

@override
void didChangeDependencies() {
  super.didChangeDependencies();
  if (widget.onWillPop != null) {
    _route?.removeScopedWillPopCallback(widget.onWillPop!);
  }
  _route = ModalRoute.of(context);
  if (widget.onWillPop != null) {
    _route?.addScopedWillPopCallback(widget.onWillPop!);
  }
}

Code after migration:

@override
void didChangeDependencies() {
  super.didChangeDependencies();
  _route?.unregisterPopEntry(this);
  _route = ModalRoute.of(context);
  _route?.registerPopEntry(this);
}

Migrating from ModalRoute.hasScopedWillPopCallback to ModalRoute.popDisposition

This method was previously used for a use-case very similar to Predictive Back but in the Cupertino library, where certain back transitions allowed canceling the navigation. The route transition was disabled when there was even the possibility of a WillPopScope widget canceling the pop.

Now that the API requires this to be decided ahead of time, this no longer needs to be speculatively based on the existence of PopScope widgets. The definitive logic of whether a ModalRoute has popping blocked by a PopScope widget is baked into ModalRoute.popDisposition.

Code before migration:

if (_route.hasScopedWillPopCallback) {
  // Disable predictive route transitions.
}

Code after migration:

if (_route.popDisposition == RoutePopDisposition.doNotPop) {
  // Disable predictive route transitions.
}

Migrating a back confirmation dialog

WillPopScope was sometimes used to show a confirmation dialog when a back gesture was received. This can still be done with PopScope in a similar pattern.

Code before migration:

WillPopScope(
  onWillPop: () async {
    final bool? shouldPop = await _showBackDialog();
    return shouldPop ?? false;
  },
  child: child,
)

Code after migration:

return PopScope(
  canPop: false,
  onPopInvokedWithResult: (bool didPop, Object? result) async {
    if (didPop) {
      return;
    }
    final NavigatorState navigator = Navigator.of(context);
    final bool? shouldPop = await _showBackDialog();
    if (shouldPop ?? false) {
      navigator.pop();
    }
  },
  child: child,
)

Supporting predictive back

For complete setup instructions, guidelines on predictive back gesture animations, and Android manifest configuration, check out how to add the predictive-back gesture.

Migration assistantAI-generated · grounded in cited sources
AI explanations are not configured
Set 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.
Official migration guide·Verified classification (90%)

Listed in the official breaking-changes index; title states deprecation.

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 explanationAI-generated · grounded in cited sources
AI explanations are not configured
Set 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.
Was this useful?