Index
In a nutshell, go_router is a convenient package for managing screen transitions.
It’s an upgrade over Navigator.
Features are as follows:
- Parsing paths and query parameters using template syntax (e.g., “user/:id”).
- Support for displaying multiple destination (sub-route) screens and performing redirects.
- Support for redirecting users to different URLs based on the application’s state, like signing in when a user is not authenticated.
- Support for multiple Navigators using ShellRoute – You can display an inner Navigator that shows custom pages based on matched routes. For example, to display a BottomNavigationBar shown at the bottom of the screen, you can use this approach.
- Supports both Material and Cupertino apps.
- Backward compatibility with the Navigator API.
For more details, please refer to the official documentation.
Let’s start by looking at the basic usage.
First, design the screen transitions.
For example, something like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
import 'package:go_router/go_router.dart'; import 'package:goroutersample/pages/page1.dart'; import 'package:goroutersample/pages/page1_detail.dart'; // Put all the screens you want to transition to here final GoRouter router = GoRouter( routes: <RouteBase>[ GoRoute(path: '/', builder: (context, state) => const Page1()), GoRoute( path: '/page1/detail', builder: (context, state) => const Page1Deatail()), ], ); |
You’ll describe the screens you want to transition to one by one like this:
1 |
GoRoute(path:'desired/path', builder:(context,state) => DesiredScreen) |
Where:
desired/path
: Specify the path, similar to a directory structure (use ‘/’ for the screen you want to show first when the app launches).
To implement screen transitions using go_router, you need to define MaterialApp.router
as shown in the following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp.router( routerConfig: router, ); } } |
Create the screens you want to transition to.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; class Page1 extends StatelessWidget { const Page1({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( body: Center( child: ElevatedButton( onPressed: () => {context.push('/page1/detail')}, child: const Text('next'), ), ), ); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; class Page1Deatail extends StatelessWidget { const Page1Deatail({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Center( child: ElevatedButton( onPressed: () => {context.go('/')}, child: const Text('back page1'), ), ); } } |
When you want to transition screens, write code like this:
1 |
context.go('destination/path') |
go('destination/path')
: Replace the current screen with the destination screen.push('destination/path')
: Stack the destination screen on top of the currently displayed screen.pop()
: Go back to the previous stack.
For more details, refer to the API reference.
Designing Screen Transitions with BottomNavigationBar
When using BottomNavigationBar, use ShellRouter().
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:goroutersample/basepage.dart'; import 'package:goroutersample/pages/home.dart'; import 'package:goroutersample/pages/page1.dart'; import 'package:goroutersample/pages/page1_detail.dart'; import 'package:goroutersample/pages/page2.dart'; import 'package:goroutersample/pages/page2_detail.dart'; // The following two lines are essential final GlobalKey<NavigatorState> _rootNavigatorKey = GlobalKey<NavigatorState>(debugLabel: 'root'); final GlobalKey<NavigatorState> _shellNavigatorKey = GlobalKey<NavigatorState>(debugLabel: 'shell'); final GoRouter router = GoRouter( navigatorKey: _rootNavigatorKey, initialLocation: '/', routes: <RouteBase>[ GoRoute( path: '/', builder: (context, state) => const HomePage(), ), // Describe screens for transition inside ShellRoute ShellRoute( navigatorKey: _shellNavigatorKey, // Describe pages implementing BottomNavigationBar // Pass Scaffold's body with child builder: (context, state, child) => BasePage( child: child, ), routes: <RouteBase>[ // Describe pages to transition from BottomNavigationBar GoRoute( path: '/page1', builder: (context, state) => const Page1(), // Describe pages to transition from Page1 routes: <RouteBase>[ GoRoute( path: 'detail', builder: (context, state) => const Page1Detail(), ), ]), ], ), // Describe pages to transition from BottomNavigationBar GoRoute( path: '/page2', builder: (context, state) => const Page2(), // Describe pages to transition from Page2 routes: <RouteBase>[ GoRoute( path: 'detail', builder: (context, state) => const Page2Detail(), ), ], ), ], ); |
The Initial Page
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; class HomePage extends StatelessWidget { const HomePage({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( body: Center( child: ElevatedButton( onPressed: (() => context.go('/page1')), child: const Text('go page1'), ), ), ); } } |
Note: Using context.push('page1')
will display the pure Page1.dart (without showing the BottomNavigationBar), so be cautious.
Implementing BottomNavigationBar
For the body’s content, receive it through the argument passed by ShellRoute.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; class BasePage extends StatefulWidget { const BasePage({Key? key, required this.child}) : super(key: key); final Widget child; @override State<BasePage> createState() => _BasePageState(); } class _BasePageState extends State<BasePage> { int _selectedIndex = 0; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(), bottomNavigationBar: NavigationBar( selectedIndex: _selectedIndex, destinations: const <Widget>[ NavigationDestination(icon: Icon(Icons.star), label: 'page1'), NavigationDestination(icon: Icon(Icons.hardware), label: 'page2'), ], onDestinationSelected: (index) { switch (index) { case 0: _selectedIndex = 0; context.push('/page1'); break; case 1: _selectedIndex = 1; context.push('/page2'); break; default: _selectedIndex = 0; context.push('/page1'); } setState(() {}); }, ), body: widget.child, ); } } |
Implementing Page 1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; class Page1 extends StatelessWidget { const Page1({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Center( child: ElevatedButton( onPressed: () => {context.push('/page1/detail')}, child: const Text('go to page1 detail'), ), ); } } |
Implementing Page 1 Detail
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; class Page1Deatail extends StatelessWidget { const Page1Deatail({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Center( child: ElevatedButton( onPressed: () => {context.pop()}, child: const Text('back page1'), ), ); } } |