flutterflutter-webflutter-routesflutter-go-router

GoRouter - Entering the url in the address bar doesn't only create the related page


I'm writing a web flutter application with go_router.

Below is a code sample to illustrate how it is architectured (you can also check out this repo).

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

void main() {
  runApp(const MyApp());
}

final router = GoRouter(
  routes: [
    GoRoute(
      path: '/',
      redirect: (_) => '/persons',
    ),
    GoRoute(
      path: '/persons',
      builder: (_, __) => const PersonsScreen(),
      routes: [
        GoRoute(
          path: ':id',
          pageBuilder: (_, __) => const DrawerPage(
            child: PersonScreen(),
          ),
        ),
      ],
    ),
  ],
);

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      routeInformationParser: router.routeInformationParser,
      routerDelegate: router.routerDelegate,
      routeInformationProvider: router.routeInformationProvider,
    );
  }
}

class PersonsScreen extends StatelessWidget {
  const PersonsScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.grey,
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            const Text('Persons'),
            ElevatedButton(
              onPressed: () => GoRouter.of(context).push('/persons/1'),
              child: const Text('Push'),
            )
          ],
        ),
      ),
    );
  }
}

class PersonScreen extends StatelessWidget {
  const PersonScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.deepPurple,
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            const Text('Person'),
            ElevatedButton(
              onPressed: GoRouter.of(context).pop,
              child: const Text('Pop'),
            )
          ],
        ),
      ),
    );
  }
}

class DrawerPage extends Page {
  const DrawerPage({
    required this.child,
  });

  final Widget child;

  @override
  Route createRoute(BuildContext context) {
    return _DrawerPageRoute(
      settings: this,
    );
  }
}

class _DrawerPageRoute extends TransitionRoute {
  _DrawerPageRoute({
    required DrawerPage settings,
  }) : super(
          settings: settings,
        );
  @override
  Iterable<OverlayEntry> createOverlayEntries() {
    return [
      OverlayEntry(
        builder: (context) {
          return Row(
            children: [
              const Spacer(flex: 2),
              Expanded(
                  child: SlideTransition(
                position: animation!.drive(
                  Tween(
                    begin: const Offset(1, 0),
                    end: const Offset(0, 0),
                  ),
                ),
                child: (settings as DrawerPage).child,
              )),
            ],
          );
        },
      ),
    ];
  }

  @override
  bool get opaque => false;

  @override
  Duration get transitionDuration => const Duration(milliseconds: 200);
}

It has 2 pages:

  1. /persons
  2. /persons/:id

/persons is just a grey screen:

Persons screen

When I click on "Push", it pushed /persons/1 which is a transparent route that takes a 3 of the screen:

Person screen

This works well and I can still see the previous screen related to /persons behind the DrawerPage. And when I click on "Pop", it does remove the purple screen (as it should).


But what I don't understand is when I paste myself the URL in the browser's address bar (for example /persons/3), it also ends up with the purple page that takes 1/3 of the screen (this is fine), but for some reason, the grey screen (linked to /persons) is behind and I can pop to go to the previous page (/persons) when I click on "Pop".

How and why is this page built? How does flutter/GoRouter decide that when going to /persons/:id it should insert persons/ as the first page?


Solution

  • This is how it works.

    According to the documentation (https://gorouter.dev/sub-routes):

    go_router will match the routes all the way down the tree of sub-routes to build up a stack of pages

    The route /persons/id includes the screen /persons in the stack and that is why it is shown.

    If you want a screen showing /persons/id without the stack, you could define a different route where /persons/id is the root. Like:

    final router = GoRouter(
      routes: [
        GoRoute(
          path: '/',
          redirect: (_) => '/persons',
        ),
        GoRoute(
          path: '/persons',
          builder: (_, __) => const PersonsScreen(),
          routes: [
            GoRoute(
              path: ':id',
              pageBuilder: (_, __) => const DrawerPage(
                child: PersonScreen(),
              ),
            ),
          ],
        ),
        GoRoute(
          path: '/person/:id',
          pageBuilder: (_, __) => const DrawerPage(
            child: PersonScreen(),
          ),
        ),
      ],
    );