Exploring GoRouter in Flutter: A Guide to Efficient Navigation
A beginner's guide to exploring GoRouter in Flutter
Introduction
Navigating between screens in Flutter is crucial for any app, and while the built-in Navigator API provides functionality, it can get complex for larger projects. This is where go_router shines, offering a more declarative, URL-based, and feature-rich navigation system. This article delves deep into every detail of go_router, guiding you from setup to advanced features like redirection and nested routes.
go_router
is a flexible and lightweight routing library for Flutter that simplifies the navigation process and provides a clean API for managing routes, passing parameters, and handling redirects. It is designed to be easy to use while offering advanced features for more complex navigation requirements.
Navigation plays a crucial role in crafting seamless user experiences. While the built-in Navigator 2.0 offers versatility, it can become complex in larger projects. Here's where go_router
steps in, providing a declarative, URL-based, and feature-rich navigation solution that simplifies the process significantly.
Installation
- Add
go_router
to yourpubspec.yaml
file:
YAML
dependencies:
go_router: ^13.0.0
- Import it in your Dart files:
Dart
import 'package:go_router/go_router.dart';
Defining Routes
- Create a list of
GoRoute
objects, each defining a route:
Dart
final routes = [
GoRoute(
path: '/',
builder: (context, state) => const HomeScreen(),
),
GoRoute(
path: '/products/:id',
builder: (context, state) => ProductDetailsScreen(productId: state.params['id']!),
),
// ... more routes
];
Creating the Router
- Instantiate a
GoRouter
object, passing the routes and integrating it with your app's MaterialApp:
Dart
MaterialApp.router(
routeInformationParser: GoRouter.of(context).routeInformationParser,
routerDelegate: GoRouter(routes: routes),
// ... other MaterialApp properties
)
Navigating Between Screens
- Use
GoRouter.of(context).go()
to navigate programmatically:
Dart
GoRouter.of(context).go('/products/123');
- Navigate using named routes:
Dart
GoRouter.of(context).goNamed('productDetails', params: {'id': 123});
Passing Parameters
- Pass query parameters:
Dart
GestureDetector(
onTap: () => context.goNamed( ProductDetailsScreen.routeName,
queryParameters: {'id': product.id}, ),
child: SingleProduct(product: product),
);
- Access them in the destination screen:
Dart
GoRoute(
path: ProductDetailsScreen.routeName,
name: ProductDetailsScreen.routeName,
builder: (BuildContext context, GoRouterState state) {
return ProductDetailsScreen(
productId: state.uri.queryParameters['id'] ?? "",
);
},
)
- Pass path parameters:
Dart
context.goNamed(
'pay-now',
pathParameters: <String, String>{
'description': product.description,
},
);
- Retrieve them in the destination screen:
Dart
GoRoute(
path: 'product-purchase/:description',
name: ProductPurchaseScreen.routeName,
builder: (BuildContext context, GoRouterState state) {
return ProductPurchaseScreen(
description: state.pathParameters['description']!,
);
},
)
Sub-Routes and ShellRoute
- Define nested routes using the
children
property:
Dart
GoRoute(
path: '/profile',
builder: (context, state) => ProfileScreen(),
children: [
GoRoute(
path: 'settings',
builder: (context, state) => SettingsScreen(),
),
],
),
- Use
StatefulShellRoute
for persistent tab navigation:
Dart
GoRoute(
path: '/',
builder: (context, state) => HomeScreen(),
children: [
ShellRoute(
builder: (context, state) => const BottomNavigationBar(
// ...
),
routes: [
// ... tab routes
],
),
],
),
Redirection and Guards
- Redirect users with the
redirect
property:
Dart
GoRoute(
path: '/old-path',
redirect: (state) => '/new-path',
),
- Control navigation with guards:
Dart
GoRoute(
path: '/admin',
guard: (context, state) => isAdmin,
builder: (context, state) => AdminScreen(),
),
Setting Up a Real Flutter Project using Go Router
Before diving into GoRouter, let's start by setting up a new Flutter project and organizing the codebase. The project structure includes the following folders and files:
go_router_project/
|-- lib/
| |-- main.dart
| |-- models/
| | |-- product.dart
| |-- controller/
| | |-- product_controller.dart
| |-- config/
| | |-- route_config.dart
| |-- screens/
| | |-- product_details_screen.dart
| | |-- product_list_screen.dart
| | |-- product_purchase_screen.dart
| |-- widgets/
| |-- bottom_container.dart
| |-- color_container.dart
| |-- ratings.dart
| |-- search_section.dart
| |-- show_modal.dart
| |-- single_product.dart
|-- pubspec.yaml
Now, open the pubspec.yaml
file and add the following dependency:
dependencies:
go_router: ^13.0.0
Save the file and run flutter pub get
in the terminal to fetch the dependency.
We'll be creating a minimalistic shopping app with just three pages.
Project Structure:
main.dart:
Replace the code in lib/main.dart
with this
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'go_router/config/route_config.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
SystemChrome.setSystemUIOverlayStyle(
const SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
statusBarIconBrightness: Brightness.dark,
),
);
return MaterialApp.router(
title: 'Flutter GoRouter',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.brown),
useMaterial3: true,
),
routerConfig: router, // go router
);
}
}
- Model
product.dart:
Add this to lib/models/product.dart
import 'package:flutter/foundation.dart';
class Product {
final String id;
final String name;
final String imageUrl;
final String description;
final double price;
final double previousPrice;
final String colors;
Product({
required this.id,
required this.name,
required this.imageUrl,
required this.description,
required this.previousPrice,
required this.price,
required this.colors,
});
factory Product.initial() => Product(
id: '',
name: '',
imageUrl: '',
description: '',
previousPrice: 0.0,
price: 0.0,
colors: '',
);
}
- Controller
product_controller.dart:
Add this to lib/controllers/product_controller.dart
import '../models/product.dart';
class ProductController {
Product findById(String? id) {
return _products.firstWhere((product) => product.id == id);
}
List<Product> get products => _products;
final List<Product> _products = [
Product(
id: 'p7',
name: 'Leather BackPack',
imageUrl:
'https://images.unsplash.com/photo-1571689936114-b16146c9570a?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxzZWFyY2h8NzR8fHByb2R1Y3R8ZW58MHx8MHx8&auto=format&fit=crop&w=800&q=60',
description:
'The stronger the better it is to load it with all that the eyes sees useful and needful too. BackPack is a all-fit leather strong bag for carrying anything the hands can store and it\'s literally worth any penny',
price: 30.9,
previousPrice: 40.9,
colors: 'red,grey,black,indigo,purple',
),
Product(
id: 'p1',
name: 'Smart Watch',
imageUrl:
'https://images.unsplash.com/photo-1523275335684-37898b6baf30?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxzZWFyY2h8Mnx8cHJvZHVjdHxlbnwwfHwwfHw%3D&auto=format&fit=crop&w=800&q=60',
description: 'A white smart watch with good features and more',
price: 29.99,
previousPrice: 39.99,
colors: 'red,grey,black,indigo,purple',
),
Product(
id: 'p16',
name: 'PowerBook',
imageUrl:
'https://get.pxhere.com/photo/laptop-computer-macbook-mac-screen-water-board-keyboard-technology-air-mouse-photo-airport-aircraft-tablet-aviation-office-black-monitor-keys-graphic-hardware-image-pc-exhibition-multimedia-calculator-vector-water-cooling-floppy-disk-phased-out-desktop-computer-netbook-personal-computer-computer-monitor-electronic-device-computer-hardware-display-device-448748.jpg',
description:
'Awesome hardware, crappy keyboard and a hefty price. Buy now before a one is released!',
price: 2299.99,
previousPrice: 3299.99,
colors: 'red,grey,black,indigo,purple',
),
Product(
id: 'p2',
name: 'Red Sneakers',
imageUrl:
'https://images.unsplash.com/photo-1542291026-7eec264c27ff?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxzZWFyY2h8MTB8fHByb2R1Y3R8ZW58MHx8MHx8&auto=format&fit=crop&w=800&q=60',
description:
'Perfect for your joggers and black T-shirts and more. The sneakers comes in different sizes and colors. You never know when that T-shirt needs some styles with the soft layers of a sneakers',
price: 199.99,
previousPrice: 299.99,
colors: 'yellow,grey,black,red,teal',
),
Product(
id: 'p3',
name: 'Nikon Camera',
imageUrl:
'https://images.unsplash.com/photo-1564466809058-bf4114d55352?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxzZWFyY2h8MjB8fHByb2R1Y3R8ZW58MHx8MHx8&auto=format&fit=crop&w=800&q=60',
description:
'You can only see clearer with your eyes but a camera saves the memory in it\'s eyes',
price: 89.9,
previousPrice: 109.9,
colors: 'red,grey,black,indigo,purple',
),
Product(
id: 'p4',
name: 'HeadSets',
imageUrl:
'https://images.unsplash.com/photo-1583394838336-acd977736f90?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxzZWFyY2h8MjJ8fHByb2R1Y3R8ZW58MHx8MHx8&auto=format&fit=crop&w=800&q=60',
description:
'The louder the sound, the better it feels inside with the body',
price: 120.1,
previousPrice: 150.1,
colors: 'red,grey,black,indigo,purple',
),
Product(
id: 'p5',
name: 'Amazon SoundBox',
imageUrl:
'https://images.unsplash.com/photo-1543512214-318c7553f230?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxzZWFyY2h8MjR8fHByb2R1Y3R8ZW58MHx8MHx8&auto=format&fit=crop&w=800&q=60',
description:
'Automated soundbox with voice recognition and more. What could be more better',
price: 78.19,
previousPrice: 88.19,
colors: 'red,grey,black,indigo,purple',
),
Product(
id: 'p6',
name: 'Xbox 360 GamePads',
imageUrl:
'https://images.unsplash.com/photo-1600080972464-8e5f35f63d08?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxzZWFyY2h8Mzd8fHByb2R1Y3R8ZW58MHx8MHx8&auto=format&fit=crop&w=800&q=60',
description:
'You never know when it is time to touch it better except the pads with xbox is there to assist',
price: 98.99,
previousPrice: 108.99,
colors: 'red,grey,black,indigo,purple',
),
];
}
- Config
route_config.dart:
Add this to lib/config/route_config.dart
import 'package:flutter/material.dart';
import 'package:get_it_auto_router_go_router/go_router/controllers/product_controller.dart';
import 'package:go_router/go_router.dart';
import '../models/product.dart';
import '../screens/product_details_screen.dart';
import '../screens/product_list_screen.dart';
import '../screens/product_purchase_screen.dart';
/// The route configuration.
final GoRouter router = GoRouter(
routes: <RouteBase>[
GoRoute(
path: '/',
builder: (BuildContext context, GoRouterState state) {
return const ProductListScreen();
},
routes: <RouteBase>[
GoRoute(
path: ProductDetailsScreen.routeName,
name: ProductDetailsScreen.routeName,
builder: (BuildContext context, GoRouterState state) {
return ProductDetailsScreen(
productId: state.uri.queryParameters['id'] ?? "",
);
},
routes: <RouteBase>[
GoRoute(
path: 'product-purchase/:description',
name: ProductPurchaseScreen.routeName,
builder: (BuildContext context, GoRouterState state) {
return ProductPurchaseScreen(
productImage: state.uri.queryParameters['img']!,
productPrice: state.uri.queryParameters['price']!,
productName: state.uri.queryParameters['name']!,
description: state.pathParameters['description']!,
);
},
onExit: (BuildContext context) async {
final bool? confirmed = await showDialog<bool>(
context: context,
builder: (_) {
return AlertDialog(
content: const Text('Are you sure to leave this page?'),
actions: <Widget>[
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: const Text('Cancel'),
),
TextButton(
onPressed: () => Navigator.of(context).pop(true),
child: const Text('Confirm'),
),
],
);
},
);
return confirmed ?? false;
},
)
],
)
],
),
],
);
- Screens
product_list_screen.dart:
Add this to lib/screens/product_list_screen.dart
import 'package:flutter/material.dart';
import 'package:get_it_auto_router_go_router/go_router/controllers/product_controller.dart';
import 'package:get_it_auto_router_go_router/go_router/screens/product_details_screen.dart';
import 'package:get_it_auto_router_go_router/go_router/widgets/search_section.dart';
import 'package:get_it_auto_router_go_router/go_router/widgets/single_product.dart';
import 'package:go_router/go_router.dart';
import '../models/product.dart';
class ProductListScreen extends StatelessWidget {
const ProductListScreen({super.key});
@override
Widget build(BuildContext context) {
ProductController productController = ProductController();
TextEditingController searchController = TextEditingController();
return Scaffold(
appBar: AppBar(
title: const Text('Products'),
elevation: 0,
),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
SearchSection(
searchController: searchController,
),
const SizedBox(height: 10),
Expanded(
child: GridView.builder(
itemCount: productController.products.length,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 10,
crossAxisSpacing: 10,
),
itemBuilder: (context, index) {
Product product = productController.products[index];
return GestureDetector(
onTap: () => context.goNamed(
ProductDetailsScreen.routeName,
queryParameters: {'id': product.id},
),
child: SingleProduct(product: product),
);
},
),
),
],
),
),
);
}
}
product_details_screen.dart:
Add this to lib/screens/product_details_screen.dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:go_router/go_router.dart';
import '../controllers/product_controller.dart';
import '../models/product.dart';
import '../widgets/bottom_container.dart';
import '../widgets/color_container.dart';
import '../widgets/ratings.dart';
import '../widgets/show_modal.dart';
class ProductDetailsScreen extends StatelessWidget {
static const routeName = 'product-details';
final String productId;
const ProductDetailsScreen({
super.key,
required this.productId,
});
@override
Widget build(BuildContext context) {
late Color colored;
// get color
Color getColor(String color) {
switch (color) {
case 'red':
colored = Colors.red;
break;
case 'purple':
colored = Colors.purple;
break;
case 'grey':
colored = Colors.grey;
break;
case 'black':
colored = Colors.black;
break;
case 'orange':
colored = Colors.orange;
break;
case 'indigo':
colored = Colors.indigo;
break;
case 'yellow':
colored = Colors.yellow;
break;
case 'blue':
colored = Colors.blue;
break;
case 'brown':
colored = Colors.brown;
break;
case 'teal':
colored = Colors.teal;
break;
default:
}
return colored;
}
ProductController productController = ProductController();
Product product = productController.findById(productId);
List<String> availableColors = product.colors.split(',');
// pay now
void payNow() {
context.goNamed(
'pay-now',
pathParameters: <String, String>{
'description': product.description,
},
queryParameters: <String, String>{
'img': product.imageUrl.toString(),
'price': product.price.toString(),
'name': product.name.toString(),
},
);
}
return Scaffold(
extendBodyBehindAppBar: true,
appBar: AppBar(
automaticallyImplyLeading: false,
backgroundColor: Colors.transparent,
elevation: 0,
systemOverlayStyle: const SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
),
leading: IconButton(
icon: const Icon(Icons.arrow_back),
color: Colors.black,
onPressed: () {
Navigator.of(context).pop();
},
),
),
body: Column(
children: [
Expanded(
flex: 2,
child: GestureDetector(
onTap: () => showImageModal(context, product),
child: ClipRRect(
borderRadius: const BorderRadius.vertical(
top: Radius.zero,
bottom: Radius.circular(50),
),
child: Hero(
tag: product.id,
child: Image.network(
product.imageUrl,
fit: BoxFit.cover,
width: double.infinity,
),
),
),
),
),
Expanded(
flex: 3,
child: Padding(
padding: const EdgeInsets.all(15.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
product.name,
style: const TextStyle(
fontSize: 30,
),
),
const SizedBox(height: 5),
ratings(),
const SizedBox(height: 5),
Row(
children: [
Text(
'\$${product.price.toString()}',
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
const SizedBox(width: 3),
Text(
'\$${product.previousPrice.toString()}',
style: const TextStyle(
fontSize: 15,
color: Colors.grey,
decoration: TextDecoration.lineThrough,
),
),
],
),
const Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
'Available in stocks',
style: TextStyle(
fontWeight: FontWeight.bold,
),
),
Text(
'Out of stocks',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.deepOrange,
decoration: TextDecoration.lineThrough,
),
),
],
),
const SizedBox(height: 10),
const Text(
'Colors Available',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 15,
),
),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
for (var color in availableColors)
buildContainer(
color,
getColor,
)
],
),
const SizedBox(height: 15),
const Text(
'About',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 15,
),
),
const SizedBox(height: 10),
Text(
product.description,
textAlign: TextAlign.justify,
),
],
),
),
),
],
),
bottomSheet: bottomContainer(product, payNow),
);
}
}
product_purchase_screen.dart:
Add this to lib/screens/product_purchase_screen.dart
import 'package:flutter/material.dart';
class ProductPurchaseScreen extends StatelessWidget {
const ProductPurchaseScreen({
super.key,
required this.productImage,
required this.productName,
required this.productPrice,
required this.description,
});
static const routeName = 'pay-now';
final String productName;
final String productPrice;
final String productImage;
final String description;
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: const FloatingActionButton(
onPressed: null,
child: Icon(
Icons.check_circle,
),
),
appBar: AppBar(
title: const Text('Purchase Item'),
),
body: SingleChildScrollView(
child: Center(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: <Widget>[
ClipRRect(
borderRadius: BorderRadius.circular(10),
child: Image.network(productImage),
),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
productName,
style: const TextStyle(
fontWeight: FontWeight.w800,
fontSize: 18,
),
),
Text(
'\$$productPrice',
style: const TextStyle(
fontWeight: FontWeight.w800,
fontSize: 16,
color: Colors.grey,
),
)
],
),
const SizedBox(height: 10),
Text(
description,
style: const TextStyle(
fontSize: 16,
),
),
],
),
),
),
),
);
}
}
Widgets
bottom_container.dart:
Add this to lib/widgets/bottom_container.dart
// bottom container
import 'package:flutter/material.dart';
import '../models/product.dart';
Container bottomContainer(Product productDetails,Function payNow) {
return Container(
color: Colors.white,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 18.0,
vertical: 10,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Price',
style: TextStyle(
color: Colors.grey,
fontWeight: FontWeight.w500,
fontSize: 14,
),
),
const SizedBox(height: 5),
Text(
'\$${productDetails.price}',
style: const TextStyle(
color: Colors.brown,
fontWeight: FontWeight.w700,
fontSize: 25,
),
)
],
),
Wrap(
crossAxisAlignment: WrapCrossAlignment.center,
children: [
Container(
height: 50,
width: 80,
decoration: BoxDecoration(
color: Colors.brown.withOpacity(0.3),
borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(5),
topLeft: Radius.circular(5),
),
),
child: const Center(
child: Wrap(
crossAxisAlignment: WrapCrossAlignment.center,
children: [
Icon(
Icons.shopping_cart_checkout,
color: Colors.white,
),
SizedBox(width: 15),
Text(
'1',
style: TextStyle(
color: Colors.white,
),
),
],
),
),
),
GestureDetector(
onTap: () => payNow(),
child: Container(
height: 50,
width: 120,
decoration: const BoxDecoration(
color: Colors.brown,
borderRadius: BorderRadius.only(
bottomRight: Radius.circular(5),
topRight: Radius.circular(5),
),
),
child: const Center(
child: Text(
'Buy Now',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w700,
),
),
),
),
)
],
)
],
),
),
);
}
ratings.dart:
Add this to lib/widgets/ratings.dart
import 'package:flutter/material.dart';
Widget ratings() => const Row(
children: [
Icon(Icons.star, color: Colors.deepOrange, size: 15),
Icon(Icons.star, color: Colors.deepOrange, size: 15),
Icon(Icons.star, color: Colors.deepOrange, size: 15),
Icon(Icons.star, color: Colors.deepOrange, size: 15),
Icon(Icons.star, color: Colors.deepOrange, size: 15),
SizedBox(width: 20),
Text('(3400 Reviews)')
],
);
color_container.dart:
Add this to lib/widgets/color_container.dart
// build container for color
import 'package:flutter/cupertino.dart';
Widget buildContainer(String color,Function getColor) {
return Container(
height: 5,
width: 40,
decoration: BoxDecoration(
color: getColor(color),
borderRadius: BorderRadius.circular(20),
),
);
}
search_section.dart:
Add this to lib/widgets/search_section.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class SearchSection extends StatelessWidget {
const SearchSection({
super.key,
required this.searchController,
});
final TextEditingController searchController;
@override
Widget build(BuildContext context) {
return TextField(
controller: searchController,
decoration: InputDecoration(
prefixIcon: const Icon(
CupertinoIcons.search,
color: Colors.black,
),
hintText: 'Enter search keyword',
label: const Text(
'Search Here',
),
fillColor: Colors.grey.withOpacity(0.1),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
),
);
}
}
show_modal.dart:
Add this to lib/widgets/show_modal.dart
// show modal for image
import 'package:flutter/material.dart';
import '../models/product.dart';
void showImageModal(BuildContext context,Product product) {
showDialog(
context: context,
builder: (BuildContext context) {
return Dialog(
insetPadding: const EdgeInsets.all(12),
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
child: Padding(
padding: const EdgeInsets.all(3.0),
child: Stack(children: [
ClipRRect(
borderRadius: BorderRadius.circular(20),
child: Image(
width: double.infinity,
fit: BoxFit.cover,
image: NetworkImage(product.imageUrl),
),
),
Positioned(
right: 1,
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: Colors.grey.withOpacity(0.5),
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
Text(product.name),
const SizedBox(width: 5),
Text(
'\$${product.price}',
style: const TextStyle(
fontWeight: FontWeight.bold,
),
),
],
),
),
),
)
]),
),
);
},
);
}
single_product.dart:
Add this to lib/widgets/single_product.dart
import 'package:flutter/material.dart';
import '../models/product.dart';
class SingleProduct extends StatelessWidget {
const SingleProduct({
super.key,
required this.product,
});
final Product product;
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: Colors.grey.withOpacity(0.1),
borderRadius: BorderRadius.circular(10),
),
child: Column(
children: [
ClipRRect(
borderRadius: const BorderRadius.only(
topRight: Radius.circular(10),
topLeft: Radius.circular(10),
),
child: Hero(
tag: product.id,
child: Image.network(
product.imageUrl,
height: 120,
width: double.infinity,
fit: BoxFit.cover,
),
),
),
const SizedBox(height: 10),
Text(
product.name,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 10),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('\$${product.price}'),
Text(
'\$${product.price}',
style: const TextStyle(
decoration: TextDecoration.lineThrough,
),
),
],
),
)
],
),
);
}
}
go_router
is a powerful and flexible routing library for Flutter, offering a clean and intuitive API for navigation. Whether you're building a simple app or a complex navigation structure, go_router
provides the tools you need to create a seamless user experience. By following this comprehensive guide, you should now be well-equipped to integrate and leverage go_router
in your Flutter projects.
For more advanced features and detailed code examples, refer to the official go_router
documentation: https://pub.dev/packages/go_router or GitHub repo: https://github.com/flutter/packages/blob/main/packages/go_router