Skip to content

Conversation

omar-hanafy
Copy link
Contributor

@omar-hanafy omar-hanafy commented Dec 22, 2024

Description:
This PR introduces top level onEnter callback in RouteConfiguration to allow developers to intercept and conditionally block navigation based on incoming routes. It adds an example (top_level_on_enter.dart) demonstrating its usage, such as handling referral code from /referral.

What This PR Changes:

  • Adds the onEnter callback (typedef OnEnter) to intercept route navigation before processing.
  • Implements logic for onEnter in GoRouteInformationParser.
  • Updates RouteConfiguration to include the onEnter callback.
  • Adds a new example, top_level_on_enter.dart, to demonstrate the feature.
  • Adds a test case to verify the onEnter callback functionality.

Simple usage example:

final GoRouter router = GoRouter(
  onEnter: (context, state) {
    // Save the referral code (if provided) and block navigation to /referral.
    if (state.uri.path == '/referral') {
      saveReferralCode(context, state.uri.queryParameters['code']);
      return false;
    }

    return true; // Allow navigation for all other routes.
  },
  routes: [ ... ],
);

Impact:
Enables developers to implement route-specific logic, such as support for handling action-based deep links without navigation (160602)

Pre-launch Checklist:

  • I read the [Contributor Guide] and followed the process outlined there for submitting PRs.
  • I read the [Tree Hygiene] page, which explains my responsibilities.
  • I read and followed the [relevant style guides] and ran the auto-formatter. (Unlike the flutter/flutter repo, the flutter/packages repo does use dart format.)
  • I signed the [CLA].
  • The title of the PR starts with the name of the package surrounded by square brackets, e.g., [go_router].
  • I [linked to at least one issue that this PR fixes] in the description above.
  • I updated pubspec.yaml with an appropriate new version according to the [pub versioning philosophy], or this PR is [exempt from version changes].
  • I updated CHANGELOG.md to add a description of the change, [following repository CHANGELOG style], or this PR is [exempt from CHANGELOG changes].
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is [test-exempt].
  • All existing and new tests are passing.

Added onEnter callback to enable route interception and demonstrate usage in example.
@omar-hanafy
Copy link
Contributor Author

Hi @chunhtai,

This PR introduces a top-level onEnter callback, addressing critical gap in go_router’s handling of deep links and redirect customizations. It allows developers to intercept routes, block navigation, or implement custom logic.

Given its potential impact, your feedback would be greatly appreciated.

Thank you for your time!

@chunhtai chunhtai self-requested a review January 23, 2025 22:55
@omar-hanafy
Copy link
Contributor Author

Hi @chunhtai, Following up on this PR. I've updated the implementation to:

  1. Enhance OnEnter signature to include current and next state:
typedef OnEnter = bool Function(
  BuildContext context, 
  GoRouterState currentState,
  GoRouterState nextState
);
  1. Deprecate top-level redirect in favor of OnEnter.

Let me know if you'd like any adjustments to this approach.

Provides access to GoRouter within OnEnter callback to support
navigation during early routing stages when InheritedGoRouter
is not yet available in the widget tree.
@omar-hanafy
Copy link
Contributor Author

Hi @chunhtai,

I've expanded the OnEnter callback again to include a GoRouter parameter to handle early-stage navigation. This addition addresses an edge case where navigation is needed before the InheritedGoRouter is available in the widget tree (particularly during initial app launch and deep linking).

The implementation:

  • Adds GoRouter as the fourth parameter to OnEnter
  • Internally adds new _fallbackRouter to be passed to the OnEnter callback when goRouter instance is not yet available through context.

@cedvdb
Copy link
Contributor

cedvdb commented Feb 13, 2025

The callback signature makes sense to me.

The fallback router makes less sense to me. Since it's not the right instance, why would one use it ? Could you add its usage in the example ?

@omar-hanafy
Copy link
Contributor Author

omar-hanafy commented Feb 13, 2025

Hi @cedvdb, The callback signature is indeed straightforward. The fallback router isn’t ideal in that it isn’t coming directly from the widget tree (via GoRouter.of(context)), but it serves as a backup when the Inherited widget isn’t available yet—typically on app startup. In those cases, if you try to look up the router from context, you’d get null because the tree isn’t fully built. Instead, the fallback (usually provided as this when constructing the parser) lets your onEnter callback still have a router reference so you can, for example, call router.go(...) to redirect or modify navigation.

Here’s an example usage:

GoRouter(
  onEnter: (context, currentState, nextState, router) {
    // Even if GoRouter.of(context) is null (because the tree isn’t built yet),
    // the fallback router will be non-null.
    if (nextState.uri.path == '/referral') {
      // Use the router instance (either from context or fallback) to navigate
      router.go('/handle-referral');
      return false; // Prevent default navigation.
    }
    return true;
  },
  // other config...
)

So while it isn’t “the right instance” from context when the tree isn’t ready, it’s our best–available router reference. If someone’s using the router during startup (or in any context where the inherited widget isn’t available), the fallback router provides a safe way to still programmatically navigate.

The idea is that the fallback isn’t a “dummy” or incomplete instance—it’s the same router instance (typically provided as this when constructing the parser). Its methods (like go) work independently of whether the router is currently available in the widget tree. So you can safely call router.go('/handle-referral'); withuot causing errors, even if it wasn’t obtained from the widget context (correct me if I'm wrong).

If you think this fallback might lead to unexpected behavior, one option is to document it clearly and advise that it’s only intended as a temporary solution until the tree is built. Otherwise, it’s our practical solution for early navigation calls.

@cedvdb
Copy link
Contributor

cedvdb commented Feb 13, 2025

typically provided as this

My bad, I misread the code and thought the fallback router was a parameter of the Router constructor, that we had to define ourselves, but that was the RouterInformationParser constructor I was reading.

I will try this out on a decently sized project

);

/// The signature of the onEnter callback.
typedef OnEnter = bool Function(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

look at this https://docs.google.com/document/d/1L940WGrM1YJ8BkJEpNd67fUqoqC6OsTfch4eH86E0xU/edit?resourcekey=0-9oxsWU1tLpAACTUXyNjpkA&disco=AAABLbcLXnE

the conclusion is that we should return a resolution class where the redirect login will a callback in the returned object.

This will help us to only run the redirection after we get the resolution. I think this will reduce a lot of obscure corner case where the another round of redirection starts before the previous round has finished

Copy link
Contributor

@cedvdb cedvdb Feb 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chunhtai

In general, stateful behaviors in navigation leads to unpredictable behavior on the web, that is: on the web an user can simply refresh a page and the state will be lost. Stateless redirection and re-redirection is achievable through query parameters (and is standard on the web)

I guess you could make the case that not every one has the web as a target and a stateful redirect is convenient. Still, being restrictive here by requiring statelessness should be considered imo

Maybe an enum may be more understandable than a boolean, else you gotta remember if true means "handled" or "you should handle it" and refer to the doc every time. "stop" and "next" are clearer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cedvdb yes while a class-based result might seem more expressive initially, it introduces statefulness that can be problematic on the web. We should lean toward a design that is inherently stateless, leveraging the URL (and query parameters) to persist any transient state needed during redirection.

Copy link
Contributor

@chunhtai chunhtai Feb 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, if it has to return a stateless object, then using enum will be more preferable then boolean. I vaguely remembered I made this decision because there may be resolution other than a yes and no if we were to expand this API.

As return the class, or more specifically doing the redirection in the callback is that i want to avoid corner cases like this

Resolution onEnter(...) {
  router.go('other route');
  await somethingasync();
  return false;
}

when this onEnter is called, the router.go will trigger the onEnter again before the entire redirection pipeline is finished, it is basically a non-tail recursion.

If we do this

Resolution onEnter(...) {
  return Resolution.stop(callBack: () {
      router.go('other route');
     await somethingasync();
  });
}

the route parser can finishes the redirecting pipeline before calling the callback, which will end up being a tail recursion.

My assumption is the non-tail recursion can create obscure bug.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that returning a plain boolean limits us to a yes/no decision, and as you've noted, if we let the side effect (like calling router.go('other route')) happen directly in the callback, it can trigger a reentrant call before the entire redirection pipeline is finished. That non-tail recursion is a recipe for obscure bugs.

By having the callback return a Resolution—an enum that can represent more than just “yes” or “no” - we can include a callback (or similar mechanism) that defers the actual navigation change until after the redirect pipeline is fully processed. In other words, by returning something like:

Resolution.stop(callback: () async {
  await somethingAsync();
  router.go('other route');
});

we ensure that the router.go call isn’t executed until after the parser has finished its work. This effectively makes the redirection pipeline tail-recursive, avoiding the pitfall of re-triggering onEnter mid-flight.

Also I think using an enum here is more future-proof, too .... it gives us room to add more nuanced resolutions if needed. This stateless design fits well with web navigation paradigms where state isn’t preserved across refreshes, and it avoids the pitfalls of having stateful redirection logic.

@cedvdb
Copy link
Contributor

cedvdb commented Feb 14, 2025

Imo onEnter should be made asynchronous instead. One may query a local cache inside for example.

I sent a PR on your fork that doesn't break the synchronous future usage

@omar-hanafy
Copy link
Contributor Author

@cedvdb thanks for the review, I've reviewed the changes in my last commit, and I believe I've resolved the issues effectively:

  • Converting onEnter to be asynchronous to lets us handle operations like cache queries without blocking, also added test case for that.
  • reverted the deprecation on redirectLimit and added support for redirect limit for onEnter, and added test case for the redirectionLimit with onEnter.
  • created new _processOnEnter and re-organized the parseRouteInformationWithDependencies.
  • changed fallbackRouter to router.

@chunhtai Regarding the discussion about using an enum (or even a class-based resolution) for a more expressive API,
I would recommend we stick with the current asynchronous onEnter design. It is inherently stateless, works
better with web navigation paradigms, and avoids the complications of stateful redirection. We can plan for
future enhancements—if we decide that a more nuanced resolution (using an enum) is needed—but for now, this
approach is a good improvement over the old redirect mechanism.

Look at the changes guys and let me know ur thoughts.

- Change onEnter callback signature to FutureOr<bool> for async operations.
- Update _processOnEnter for asynchronous navigation decisions.
- Use last successful match as fallback on blocked navigation, preventing recursion.
- Remove deprecated fallbackRouter parameter.
- Adjust redirect limit and loop detection for stable fallback endpoints.
- Update tests for sync, async, and loop scenarios.
- Improve documentation of design decisions.
Simplified the OnEnterResult class documentation and removed factory constructors from the sealed class. Introduced type aliases for navigation callbacks and route information state in parser.dart, and updated method signatures to use these aliases for improved readability and type safety. Also streamlined fallback logic when navigation is blocked.
@omar-hanafy omar-hanafy requested a review from chunhtai August 24, 2025 15:28
@chunhtai
Copy link
Contributor

chunhtai commented Aug 25, 2025

Unify: Fold legacy top-level redirect into onEnter as a thin adapter (i.e., if onEnter allows, call redirect; if it returns a new location, go() and return Block). Parser then handles only route-level redirects?

Yes

Restoration semantics: Should restoration/back bypass all top-level interception (both onEnter and legacy adapter) and only run route-level redirects for state updates? (This avoids “block on back” weirdness.) Preference?

We should still run onEnter when restoration, because the onEnter can be Auth or something that prevent viewing based on session. We can't skip them.

First on enter behavior can completely STOP and do nothing at all using Block

This is a good point. I almost feels like Block has to have at least a .go. doing nothing will be the same as a go(prev.uri).

This will look weird with methods like pop specially with iOS back button behavior

Can you explain a bit more?

@omar-hanafy
Copy link
Contributor Author

This is a good point. I almost feels like Block has to have at least a .go. doing nothing will be the same as a go(prev.uri).

Instead of forcing a redirect on Block(), what if we add a convenience factory: Block.go(location) for the common "block and reroute" case? This keeps Block() as a pure veto for when we truly need to stop navigation without side effects.

Can you explain a bit more?

You're right, my concern was misplaced. I was incorrectly equating pop with state restoration. I've since confirmed:

  • pop doesn't use the parser and won't trigger onEnter. Intercepting it is a job for onExit.
  • restore does use the parser, so onEnter must run to re-validate the state (e.g., auth).

I'm aligned with your proposal now. Here's my plan for the next commit:

  • Unify Guards: Adapt the legacy top-level redirect into the onEnter flow.
  • Fix Restoration: Ensure onEnter (with the redirect adapter) runs during state restoration.
  • Implement Block.go(): Add the convenience factory if you agree with me.
  • Update the Tests

@omar-hanafy
Copy link
Contributor Author

Hi @chunhtai I did adapt legacy top-level redirect into onEnter flow:

  • Order: onEnter → legacy top-level redirect (once) → route-level redirects.
  • restore now re-validates via onEnter.
  • Top-level redirect extracted to applyTopLegacyRedirect.
  • Added RouteConfiguration.normalizeUri; used across parsing/redirects.
  • Tests updated to cover order, restore behavior, limits, and error reporting.

@cedvdb
Copy link
Contributor

cedvdb commented Aug 27, 2025

doing nothing will be the same as a go(prev.uri).

Doing nothing should do nothing, not even a refresh otherwise you break the (implicit) contract that it should just stop navigation.

Instead of forcing a redirect on Block(), what if we add a convenience factory: Block.go(location) for the common "block and reroute" case?

IMO this is not necessary and may even be confusing. Block(then: ...) is clearer

@omar-hanafy
Copy link
Contributor Author

@chunhtai

Note that I didn’t wrap redirect as an onEnter adapter (go()+Block) because its semantics differ (history/limit/loop detection and restore).

Instead, I run legacy top redirect once right after onEnter allows, via applyTopLegacyRedirect, sharing redirectHistory with route-level redirects.

Parser now sequences: onEnter → legacy top redirect (once) → route-level redirects. RouteConfiguration.redirect handles only route-level; normalizeUri keeps paths stable.

@chunhtai
Copy link
Contributor

@omar-hanafy I have 2 questions

  1. Can you talk a bit about what semantics difference they have?

  2. If there is indeed a semantics difference and without a migration path to onExit, what's our current plan for toplevel redirect? are we keeping it around or are we doing a no-longer-supported-behavior breaking change

@omar-hanafy
Copy link
Contributor Author

Can you talk a bit about what semantics difference they have?

I tried implementing top‑level redirect as onEnter + Block(then: go(...)), but several tests failed because the two mechanisms don’t have identical semantics.

  1. Top-level redirect rewrites the target in the same parse cycle, while onEnter + go() is a two-step flow that changes history behavior.
  2. top-level redirect runs only once and avoids re-evaluating if the target itself triggers another top-level redirect. The onEnter adapter, however, re-parses and re-runs interception.
  3. The legacy redirect system (top-level and route-level) shares a unified redirectHistory to detect redirect chains (e.g., A → B → A). The onEnter guard has its own history to prevent loops from navigation calls made within the guard itself. An adapter can't easily unify these two distinct mechanisms.
  4. With legacy behavior, validation happens via onEnter and then the top-level redirect runs once before route-level redirects, keeping branch stacks/URLs correct. The adapter adds an extra navigation after the block commits.
  5. onEnter.then is deferred and reported without undoing navigation, while legacy redirect errors surface immediately in-cycle as an error RouteMatchList.

To maintain stability while redirect is deprecated, it’s still supported internally as a one-shot stage (applyTopLegacyRedirect) that runs after onEnter allows and before route-level redirects, sharing the same redirect history for loop detection.

If u see this differently, let me know ur thoughts.

If there is indeed a semantics difference and without a migration path to onExit, what's our current plan for toplevel redirect? are we keeping it around or are we doing a no-longer-supported-behavior breaking change

I think:

  • We keep the top-level redirect as a deprecated API, maintaining its exact runtime semantics. This PR introduces no breaking change.
  • provide a migration guide and add linting to help users move to the onEnter + router.go() pattern. onExit is a leave-guard and is not part of this migration path.
  • The redirect API will be fully removed in a future major release, at which point its behavior will no longer be supported

@chunhtai
Copy link
Contributor

chunhtai commented Sep 19, 2025

Top-level redirect rewrites the target in the same parse cycle, while onEnter + go() is a two-step flow that changes history behavior.

ah it is because the go needs to wait for one frame to register. This is not good. I have thought about moving the parsing logic to the router delegate instead of in route parser. Originally i thought put it in routeinformationparser is a good idea, now not so much.

Let's forget reimiplementing the top level redirect for now, what do you think that an onEnter that redirect multiple time in one route change creates multiple browser history entries. or do you think it should only generate one entry that contains the redirecting result?

I personally prefer the latter, but would like to hear your thought

@omar-hanafy
Copy link
Contributor Author

what do you think that an onEnter that redirect multiple time in one route change creates multiple browser history entries.

I agree that we should make one browser history entry for a logical “route change,” set to the final URL. Intermediate onEnter redirects in the same decision must not create entries.

Navigation pipeline (confirmed):
onEnter → (legacy top‑level redirect, once) → route‑level redirects → commit

  1. Route‑level redirects (and the one‑shot legacy top‑level redirect) happen inside parsing and never add history entries; we commit only the final match.

  2. Top‑level onEnter results

  • Allow() → proceed; any subsequent redirects coalesce into the same final entry.
  • Block() (no then) → hard stop; no new entry.
  • Block(then: ...) to reroute: to avoid accidental extra entries, we propose a convenience API:
// sugar for "redirect with replace semantics + loop detection"
Block(then: () => router.redirectTo('/final'));

redirectTo:

  • uses replace at the browser history layer (no new entry),
  • runs through our normal parse path (legacy top‑level redirect once + route‑level redirects),
  • preserves microtask timing for then (no reentrancy).

This prevents authors from writing Block(then: () => router.go(...)) when they intended a redirect.

Restore/back: we re‑run onEnter to re‑validate (e.g., auth). If it blocks and reroutes, redirectTo still commits with replace semantics so Back doesn’t step through blocked pages.

We’ll reflect this in the migration guide: prefer Block(then: () => router.redirectTo(...)) for guard‑initiated redirects; use push only when you intentionally want the user to be able to press Back to return (e.g., a sign‑in sheet).

@omar-hanafy
Copy link
Contributor Author

omar-hanafy commented Sep 21, 2025

and note that with my prev proposal, I am not trying to invent a duplicate of go().
I am trying to give authors a clear, explicit tool for “finish this navigation at X (no extra history),” instead of guessing their intent and overloading go() with context‑dependent behavior.

@chunhtai
Copy link
Contributor

sounds good to me. I will take a full review of this pr today

_debugCheckParentNavigatorKeys(
route.routes,
<GlobalKey<NavigatorState>>[...allowedKeys..add(route.navigatorKey)],
<GlobalKey<NavigatorState>>[...allowedKeys, route.navigatorKey],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice catch

## 16.3.0

- Adds new top level `onEnter` callback with access to current and next route states.
- Deprecates top level `redirect` in favor of `onEnter`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't usually mark deprecation in package. instead, we just break the api and bump major version.

for this case though I think we should hold off deprecation until we have a better way to migrate the old behavior.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed the deprecation wording from the CHANGELOG and switched to calling the old behavior “legacy” in docs. I also removed the @Deprecated annotations from the public redirect API to avoid surfacing warnings before we have a clear migration path.

## 0.1.0

- squatting on the package name (I'm not too proud to admit it)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's keep the trailing new line

),
),
);
} catch (e) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a demo purpose for this catch section? if no we should remove this part of code to avoid confusing reader

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed the extra catchError and turned it into true fire‑and‑forget so it doesn’t block the guard.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also removed the try catch block.

if (_lastMatchList != null) {
return SynchronousFuture<RouteMatchList>(_lastMatchList!);
}
// Fall back to parsing the current URI so Router still paints something sensible.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems dangerous though, in this case won't we just access the page that is guarded by things like auth?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we should just invoke onError call loop in this case, but we should make sure we document the behavior

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call. Falling back to parsing the blocked target could display a guarded page. I changed the behavior to surface an error and route through onException when there’s no prior route to restore, so we never add a blocked destination. I also added a doc comment explaining this.

infoState: infoState,
onCanEnter: () {
// Compose legacy top-level redirect here (one shared cycle/history)
final Uri uri = RouteConfiguration.normalizeUri(effectiveRoute.uri);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here and other place, shouldn't we normalize at the place where we normailize the state? that way we don't need to tie up the lose end in different place.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. I now normalize the URI once when synthesizing RouteInformationState so we don’t repeat normalization in downstream steps.

matchList = await onCanNotEnter();

// Hard stop (no then callback): reset history so user retries don't trigger limit
final bool hardStop = result is Block && callback == null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a callback that does nothing can also be a hard stop

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I documented the intent more clearly: Block() (no then) is the only guaranteed hard stop. We can’t reliably detect a “no‑op” closure, so Block(then: () {}) is treated as chaining by design. The Block docs now say this explicitly.


if (callback != null) {
unawaited(
Future<void>.microtask(callback).catchError((
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just curious why we do a microtask here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought I might need to schedule then via microtask to avoid re‑entrancy into the parse pipeline (e.g., if the callback navigates or opens a dialog during build), and we report errors without undoing the committed navigation.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is close to tail, I don't think it worth to introduce another async gap just make sure it run before return.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As long as the callback is not run as part of onEnter, I think we should be fine

@omar-hanafy
Copy link
Contributor Author

omar-hanafy commented Sep 22, 2025

sounds good to me.

nice!

@chunhtai Would you be interested in introducing a redirectTo callback as agreed in the next commit? or u prefer it in another PR later

@chunhtai
Copy link
Contributor

sounds good to me.

nice!

@chunhtai Would you be interested in introducing a redirectTo callback as agreed in the next commit? or u prefer it in another PR later

let's do it in another pr. I am still not sure what may be the best way to solve this. If we do a big refactor and move everything to routerdelegate, we may not have this issue at all. but in the same time it may require more work and be a breaking change


/// Top level page redirect.
/// Legacy top level page redirect.
/// This is handled via [applyTopLegacyRedirect] and runs at most once per navigation.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a new line in between first and second paragraph

/// Processes redirects by returning a new [RouteMatchList] representing the new
/// location.
/// Processes route-level redirects by returning a new [RouteMatchList] representing the new
/// location. This method now handles ONLY route-level redirects.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Our styleguide suggest first paragraph must be a brief one sentence describe the functionality. other supplementary doc needs to be another paragraph with a new line in between

final class Block extends OnEnterResult {
/// Creates a [Block] result.
///
/// The [then] callback is executed after the navigation is blocked.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These comment should be on the class's doc, the constructor doc usually used for things like what can be null and what not. basically focuses on the input restriction


/// Executed after the decision is committed.
/// Errors are reported and do not revert navigation.
final FutureOr<void> Function()? then;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need to type def this

/// Note: We don't introspect callback bodies. Even an empty closure
/// (`Block(then: () {})`) counts as chaining. Omit `then` entirely when you
/// want the hard stop behavior.
const Block({super.then});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consider split this up into

Block.then(Then this.then)// force none null then callback

and

Block.stop()


if (callback != null) {
unawaited(
Future<void>.microtask(callback).catchError((
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is close to tail, I don't think it worth to introduce another async gap just make sure it run before return.


if (callback != null) {
unawaited(
Future<void>.microtask(callback).catchError((
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As long as the callback is not run as part of onEnter, I think we should be fine

- deep-linking
- go-router
- navigation
- navigation
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a new line at the end

/// behavior.
final class Block extends OnEnterResult {
/// Creates a [Block] that stops navigation without running a follow-up
/// callback. Resets the redirection history so the next attempt is evaluated
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A new line in between the first paragraph and next

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't parse this doc.

Maybe just say

Returning a object of this constructor during onEnter callback halts the navigation completely.

const Block.stop() : super();

/// Creates a [Block] that runs [then] after the navigation is blocked.
/// Keeps the redirection history to detect loops during chained redirects.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A new line in between the first paragraph and next


/// Blocks the navigation from proceeding.
///
/// Use [Block.stop] for a "hard stop" that resets the redirection history, or
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here maybe something along the line

Returning a object of this constructor during onEnter callback halts the navigation completely.

// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// ignore_for_file: use_build_context_synchronously
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should not do ignore for file

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

whenever this lint shows up, try to check for context.isMounted before continue the operation

// Treat `Block.stop()` as the explicit hard stop.
// We intentionally don't try to detect "no-op" callbacks; any
// Block with `then` keeps history so chained guards can detect loops.
final bool hardStop = result is Block && callback == null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consider add a bool getter isStop on Block class so that we don't infer from callback== null

Adds context.mounted checks in GoRouteInformationParser and _OnEnterHandler to prevent actions on disposed contexts, improving safety during navigation and error handling. Enhances documentation for OnEnterResult and Block classes to clarify navigation blocking behavior. Removes unnecessary ignore_for_file: use_build_context_synchronously directives.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants