Passing Data Between Screens
Sending luggage on your route trips
Open interactive version (quiz + challenge)Real-world analogy
What is it?
Passing data between screens is the process of sending information from one screen to another during navigation. Flutter with GoRouter supports three main approaches: path parameters embedded in the URL, query parameters appended to the URL, and in-memory extras for complex objects. Screens can also return data when they pop, enabling two-way communication between screens.
Real-world relevance
Every real app needs to pass data between screens. When a user taps a product in a list, the detail screen needs the product ID. When a user fills out a multi-step form, each step passes accumulated data forward. When a user picks a photo from a gallery, the selected image is returned to the calling screen. Mastering data passing makes your navigation feel seamless and your code maintainable.
Key points
- Route Arguments with GoRouter — GoRouter lets you pass objects via the 'extra' parameter when navigating. The destination screen reads them from GoRouterState.
- Path Parameters — Path parameters are part of the URL itself. They identify a specific resource, like a user ID or product slug.
- Query Parameters — Query parameters are optional key-value pairs appended to the URL after a question mark. Great for filters, search terms, or optional flags.
- Typed Extras for Complex Data — When you need to pass a full object that cannot be serialized into a URL string, use the extra parameter. This keeps your URLs clean while sending rich data.
- Returning Data with pop — A screen can return a result when it pops. The caller awaits the push call to receive the result, just like waiting for a friend to come back with souvenirs.
- Null Safety with Returned Data — The returned value can be null if the user presses the back button without selecting anything. Always handle the null case gracefully.
- Route Guards with Redirect — Sometimes you need to check data before allowing navigation. GoRouter redirect can inspect state and reroute if conditions are not met.
- Deep Links and Data — Path and query parameters naturally support deep linking because they are part of the URL. Extras do not survive deep links since they are in-memory only.
- team_mvp_kit Navigation Pattern — In team_mvp_kit, routes are organized in a central router file. Each feature module defines its own routes, and data is passed through typed route classes for safety.
Code example
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
// Define a typed data class for route arguments
class ProductArgs {
final String id;
final String name;
const ProductArgs({required this.id, required this.name});
}
// Route configuration
final router = GoRouter(
routes: [
GoRoute(
path: '/',
builder: (context, state) => const HomeScreen(),
),
GoRoute(
path: '/product/:id',
builder: (context, state) {
final id = state.pathParameters['id']!;
final args = state.extra as ProductArgs?;
return ProductScreen(id: id, name: args?.name);
},
),
GoRoute(
path: '/color-picker',
builder: (context, state) => const ColorPickerScreen(),
),
],
);
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Shop')),
body: ListView(
children: [
ListTile(
title: const Text('Flutter Widget Book'),
onTap: () {
context.push(
'/product/101',
extra: const ProductArgs(
id: '101',
name: 'Flutter Widget Book',
),
);
},
),
ListTile(
title: const Text('Pick a theme color'),
onTap: () async {
final color = await context
.push<Color>('/color-picker');
if (color != null && context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Picked: $color')),
);
}
},
),
],
),
);
}
}
class ProductScreen extends StatelessWidget {
final String id;
final String? name;
const ProductScreen({super.key, required this.id, this.name});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(name ?? 'Product $id')),
body: Center(child: Text('Product ID: $id')),
);
}
}
class ColorPickerScreen extends StatelessWidget {
const ColorPickerScreen({super.key});
@override
Widget build(BuildContext context) {
final colors = [Colors.red, Colors.teal, Colors.purple];
return Scaffold(
appBar: AppBar(title: const Text('Pick a Color')),
body: ListView(
children: colors.map((c) => ListTile(
tileColor: c,
title: Text(c.toString()),
onTap: () => context.pop(c),
)).toList(),
),
);
}
}Line-by-line walkthrough
- 1. Import Flutter material library and GoRouter package for navigation.
- 2. Define a ProductArgs class with id and name fields to carry typed data between screens.
- 3. Create the GoRouter with three routes: home at root, product with a path parameter, and color-picker.
- 4. The product route reads the id from pathParameters and optionally reads ProductArgs from extra.
- 5. HomeScreen is a StatelessWidget with a ListView of navigation options.
- 6. The first ListTile navigates to product screen using context.push with both a path param and extra args.
- 7. The second ListTile navigates to color-picker and awaits the returned Color value.
- 8. After the push completes, check if color is not null and context is still mounted before showing a SnackBar.
- 9. ProductScreen receives id and optional name through its constructor, displaying them in the AppBar and body.
- 10. ColorPickerScreen shows a list of colors. Tapping one calls context.pop(c) to return the selected color.
- 11. The map function creates a ListTile for each color, with tileColor set to visually preview each option.
Spot the bug
class DetailScreen extends StatelessWidget {
const DetailScreen({super.key});
@override
Widget build(BuildContext context) {
final args = GoRouterState.of(context).extra as UserData;
return Scaffold(
appBar: AppBar(title: Text(args.name)),
body: ElevatedButton(
onPressed: () async {
final result = context.push<bool>('/confirm');
if (result) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Confirmed!')),
);
}
},
child: const Text('Confirm'),
),
);
}
}Need a hint?
Show answer
Explain like I'm 5
Fun fact
Hands-on challenge
More resources
- GoRouter: Route Parameters (pub.dev)
- Flutter Navigation and Routing (flutter.dev)
- GoRouter Extra Codec (pub.dev)