# Redirection

Sometimes you want your app to redirect to a different location. go_router
allows you to do this at a top level for each new navigation event or at the
route level for a specific route.

## Top-level redirection

Sometimes you want to guard pages from being accessed when they shouldn't be,
e.g. when the user is not yet logged in. For example, assume you have a class
that tracks the user's login info:

```dart
class LoginInfo extends ChangeNotifier {
  var _userName = '';
  String get userName => _userName;
  bool get loggedIn => _userName.isNotEmpty;

  void login(String userName) {
    _userName = userName;
    notifyListeners();
  }

  void logout() {
    _userName = '';
    notifyListeners();
  }
}
```

You can use this info in the implementation of a `redirect` function that you
pass as to the `GoRouter` constructor:

```dart
class App extends StatelessWidget {
  final loginInfo = LoginInfo();
  ...
  late final _router = GoRouter(
    routes: [
      GoRoute(
        path: '/',
        builder: (context, state) => HomeScreen(families: Families.data),
      ),
      ...,
      GoRoute(
        path: '/login',
        builder: (context, state) => LoginScreen(),
      ),
    ],

    // redirect to the login page if the user is not logged in
    redirect: (state) {
      // if the user is not logged in, they need to login
      final loggedIn = loginInfo.loggedIn;
      final loggingIn = state.subloc == '/login';
      if (!loggedIn) return loggingIn ? null : '/login';

      // if the user is logged in but still on the login page, send them to
      // the home page
      if (loggingIn) return '/';

      // no need to redirect at all
      return null;
    },
  );
}
```

In this code, if the user is not logged in, we either let them continue to the
`/login` location that they were already headed to by returning `null` or we
redirect them to `/login` by returning `'/login'`.

On the other hand, if the user is already logged in but headed to `/login`, we
redirect them to `/` instead.

Finally, if the user is logged in and heading to somewhere besides `/login`, we
just return `null`, letting them continue on their merry way. The `redirect`
function will be called over and over until `null` is returned to enable
[multiple redirects](#multiple-redirections).

To make it easy to access the login info wherever it's needed in the app,
consider using a state management option like
[provider](https://pub.dev/packages/provider) to put the login info into the
widget tree:

```dart
class App extends StatelessWidget {
  final loginInfo = LoginInfo();

  // add the login info into the tree as app state that can change over time
  @override
  Widget build(BuildContext context) => ChangeNotifierProvider<LoginInfo>.value(
        value: loginInfo,
        child: MaterialApp.router(...),
      );
  ...
}
```

With the login info in the widget tree, you can easily implement your login
screen:

```dart
class LoginScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) => Scaffold(
    appBar: AppBar(title: Text(_title(context))),
    body: Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          ElevatedButton(
            onPressed: () {
              // log a user in, letting all the listeners know
              context.read<LoginInfo>().login('test-user');

              // go home
              context.go('/');
            },
            child: const Text('Login'),
          ),
        ],
      ),
    ),
  );
}
```

In this case, we've logged the user in and manually redirected them to the home
screen. That's because go_router doesn't know that the app's state has changed
in a way that affects the route. If you'd like to have the app's state cause
go_router to automatically redirect, you can use the `refreshListenable`
argument of the `GoRouter` constructor:

```dart
class App extends StatelessWidget {
  final loginInfo = LoginInfo();
  ...
  late final _router = GoRouter(
    routes: ...,
    redirect: ...

    // changes on the listenable will cause the router to refresh it's route
    refreshListenable: loginInfo,
  );
}
```

Since the `loginInfo` is a `ChangeNotifier`, it will notify listeners when it
changes. By passing it to the `GoRouter` constructor, go_router will
automatically refresh the route when the login info changes. This allows you to
simplify the login logic in your app:

```dart
onPressed: () {
  // log a user in, letting all the listeners know
  context.read<LoginInfo>().login('test-user');

  // router will automatically redirect from /login to / using
  // refreshListenable
  //context.go('/');
},
```

The use of the top-level `redirect` and `refreshListenable` together is
recommended because it will handle the routing automatically for you when the
app's data changes.

### Refreshing with a `Stream`

If your route-driving state is not available as a `Listenable` but instead as a
`Stream`, you can wrap your stream with a `GoRouterRefreshStream`. This makes it
possible for `GoRouter` to react to a stream-based state managmenet solution
like [flutter_bloc](https://pub.dev/packages/flutter_bloc).

```dart
class App extends StatelessWidget {
  final streamController = StreamController();
  ...
  late final _router = GoRouter(
    routes: ...,
    redirect: ...

    // changes on the listenable will cause the router to refresh it's route
    refreshListenable: GoRouterRefreshStream(streamController.stream),
  );
}
```

## Route-level redirection

The top-level redirect handler passed to the `GoRouter` constructor is handy
when you want a single function to be called whenever there's a new navigation
event and to make some decisions based on the app's current state. However, in
the case that you'd like to make a redirection decision for a specific route (or
sub-route), you can do so by passing a `redirect` function to the `GoRoute`
constructor:

```dart
final _router = GoRouter(
  routes: [
    GoRoute(
      path: '/',
      redirect: (_) => '/family/${Families.data[0].id}',
    ),
    GoRoute(
      path: '/family/:fid',
      builder: ...,
  ],
);
```

In this case, when the user navigates to `/`, the `redirect` function will be
called to redirect to the first family's page. Redirection will only occur on
the last sub-route matched, so you can't have to worry about redirecting in the
middle of a location being parsed when you're already on your way to another
page anyway.

### Considerations

There are a couple of subtleties to consider when using route-level redirection.
If you have a route that has a `redirect` function that only redirects under
certain conditions, you will still need to provide a `builder` function, e.g.

```dart
GoRoute(
  path: '/',
  redirect: (_) => kGoElsewhere ? '/elsewhere' : null,
  builder: (context, state) => ..., // need this if kGoElsewhere == false
)
```

The `builder` function will be called when the redirect condition is false.

Also, if the `redirect` function is part of a sub-route, you will need to
provide a `builder` function, e.g.

```dart
GoRoute(
  path: '/profile',
  redirect: (_) => '/profile/home', // only called when going to /profile
  builder: (c, s) => ..., // need this to build /profile/:section stack
  routes: [
    GoRoute(
      path: ':section',
      builder: ...ProfileScreen(state.params['section']!)...,
    ),
  ],
)
```

The `redirect` function will only be called when it's the top-most route of a
stack of matched routes, so if the `redirect` is provided on a route in the
middle of the stack, the `builder` will be called when building pages for the
stack above.

In fact, you probably don't want to have a `redirect` in the middle of your
stack of pages anyway. You probably want the `redirect` function on a stack all
by itself, redirecting to another top-level route:

```dart
GoRoute(
  path: '/profile',
  redirect: (_) => '/profile/home', // builder not needed in this case
),
GoRoute
  path: '/profile/:section',
  builder: ...ProfileScreen(state.params['section']!)...,
)
```

Of course, in you don't have a route-level `redirect` function at all in your
route, you will need a `builder` function in that case as well.

## Parameterized redirection

In some cases, a path is parameterized, and you'd like to redirect with those
parameters in mind. You can do that with the `params` argument to the `state`
object passed to the `redirect` function:

```dart
GoRoute(
  path: '/author/:authorId',
  redirect: (state) => '/authors/${state.params['authorId']}',
),
```

## Multiple redirections

It's possible to redirect multiple times w/ a single navigation, e.g. `/ => /foo
=> /bar`. This is handy because it allows you to build up a list of routes over
time and not to worry so much about attempting to trim each of them to their
direct route. Furthermore, it's possible to redirect at the top level and at the
route level in any number of combinations.

If you redirect too many times, that's likely to indicate a bug in your app. By
default, more than 5 redirections will cause an exception. You can change this
by setting the `redirectLimit` argument to the `GoRouter` constructor.

The other trouble you need worry about is getting into a loop, e.g. `/ => /foo
=> /`. If that happens, you'll get an exception.

## Example: Redirection and Query Parameters

Sometimes you're doing [deep linking](/declarative-routing#deep-linking) and
you'd like a user to login first before going to the location that represents
the deep link. In that case, you can use [query
parameters](/parameters#query-parameters) in the redirect function:

```dart
class App extends StatelessWidget {
  final loginInfo = LoginInfo();
  ...
  late final _router = GoRouter(
    routes: ...,

    // redirect to the login page if the user is not logged in
    redirect: (state) {
      // if the user is not logged in, they need to login
      final loggedIn = loginInfo.loggedIn;
      final loggingIn = state.subloc == '/login';

      // bundle the location the user is coming from into a query parameter
      final fromp = state.subloc == '/' ? '' : '?from=${state.subloc}';
      if (!loggedIn) return loggingIn ? null : '/login$fromp';

      // if the user is logged in, send them where they were going before (or
      // home if they weren't going anywhere)
      if (loggingIn) return state.queryParams['from'] ?? '/';

      // no need to redirect at all
      return null;
    },

    // changes on the listenable will cause the router to refresh it's route
    refreshListenable: loginInfo,
  );
}
```

In this example, if the user isn't logged in, they're redirected to `/login`
with a `from` query parameter set to the deep link. When deciding on the value
of the `from` parameter, the `state` object has the `location` and the `subloc`
to choose from. The `location` includes any query parameters on the location
whereas the `subloc` does not. For simplicity, we'll just use the `subloc`.
However, since the query parameters will be URL encoded, you could use the
`location` instead. Dealer's choice.

Once the user logs in, the router will be triggered to refresh the route,
resulting in a call to `redirect`. In this case, since they're now logged in but
at the `/login` location, they'll be sent to either the `from` link in the query
parameters, if there is one, or to `/` otherwise.

And that's it. The combination of the `redirect` function and the
`refreshListenable` means that the login page doesn't even have to worry about
the `from` value -- it's just sent in the location during redirection and then
stripped off when it's no longer needed. Easy peasy lemon squeezy.

This same technique can be extended to include more than just the login page, as
shown in the [loading page
example](https://github.com/csells/go_router/blob/main/go_router/example/lib/loading_page.dart).