第12讲:入门级状态管理方案 - Provider详解
在上一讲中,我们深入学习了如何使用setState管理局部状态。Text('订单金额: ¥${cart.totalAmount.toStringAsFixed(2)}'),User _user = User(name: '张三', email: 'zhangsan@example.com');Text('商品数量: ${cart.totalItems}'),return Text('商品数量: $
使用Provider优雅地管理跨组件共享状态,告别繁琐的回调传递。
你好,欢迎回到《Flutter入门到精通》专栏。在上一讲中,我们深入学习了如何使用setState管理局部状态。但当状态需要在多个Widget之间共享时,setState就显得力不从心了。今天,我们将学习Flutter社区最受欢迎的状态管理方案之一——Provider。
一、为什么需要状态管理?
1.1 setState 的局限性回顾
考虑这样一个场景:用户信息需要在多个页面共享
dart
复制
下载
// 使用setState实现,需要层层传递回调 - 非常繁琐!
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
User _user = User(name: '张三', email: 'zhangsan@example.com');
void _updateUser(User newUser) {
setState(() {
_user = newUser;
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage(
user: _user,
onUserUpdated: _updateUser,
),
routes: {
'/profile': (context) => ProfilePage(
user: _user,
onUserUpdated: _updateUser,
),
'/settings': (context) => SettingsPage(
user: _user,
onUserUpdated: _updateUser,
),
},
);
}
}
1.2 Provider 的优势
-
减少样板代码:无需手动传递状态和回调
-
清晰的关注点分离:状态逻辑与UI逻辑分离
-
性能优化:只有依赖状态的Widget会重建
-
易于测试:可以轻松模拟状态进行测试
-
官方推荐:Flutter团队推荐的状态管理方案
二、Provider 核心概念
2.1 Provider 的三要素
-
ChangeNotifier:存储状态并通知监听者
-
ChangeNotifierProvider:在Widget树中提供状态
-
Consumer/Provider.of:在Widget中访问状态
2.2 工作原理
text
复制
下载
ChangeNotifier(状态)
↓ 通知变化
ChangeNotifierProvider(提供者)
↓ 提供数据
Consumer(消费者)→ 重建UI
三、Provider 实战:购物车案例
让我们通过一个完整的购物车案例来学习Provider的使用。
步骤1:添加依赖
在 pubspec.yaml 中添加Provider依赖:
yaml
复制
下载
dependencies:
flutter:
sdk: flutter
provider: ^6.1.1 # 添加Provider依赖
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^2.0.0
运行 flutter pub get 安装依赖。
步骤2:创建模型类
lib/models/product.dart
dart
复制
下载
class Product {
final String id;
final String name;
final String description;
final double price;
final String imageUrl;
const Product({
required this.id,
required this.name,
required this.description,
required this.price,
required this.imageUrl,
});
// 拷贝方法,用于创建修改后的副本
Product copyWith({
String? id,
String? name,
String? description,
double? price,
String? imageUrl,
}) {
return Product(
id: id ?? this.id,
name: name ?? this.name,
description: description ?? this.description,
price: price ?? this.price,
imageUrl: imageUrl ?? this.imageUrl,
);
}
}
步骤3:创建购物车项模型
lib/models/cart_item.dart
dart
复制
下载
class CartItem {
final Product product;
int quantity;
CartItem({
required this.product,
this.quantity = 1,
});
double get totalPrice => product.price * quantity;
// 拷贝方法
CartItem copyWith({
Product? product,
int? quantity,
}) {
return CartItem(
product: product ?? this.product,
quantity: quantity ?? this.quantity,
);
}
}
步骤4:创建购物车状态管理类
lib/providers/cart_provider.dart
dart
复制
下载
import 'package:flutter/foundation.dart';
import '../models/cart_item.dart';
import '../models/product.dart';
class CartProvider with ChangeNotifier {
// 存储购物车商品
final List<CartItem> _items = [];
// 获取购物车中的所有商品(只读)
List<CartItem> get items => List.unmodifiable(_items);
// 获取购物车商品总数
int get totalItems {
return _items.fold(0, (total, item) => total + item.quantity);
}
// 获取购物车总金额
double get totalAmount {
return _items.fold(0.0, (total, item) => total + item.totalPrice);
}
// 添加商品到购物车
void addItem(Product product) {
final index = _items.indexWhere((item) => item.product.id == product.id);
if (index >= 0) {
// 商品已存在,增加数量
_items[index] = _items[index].copyWith(
quantity: _items[index].quantity + 1,
);
} else {
// 商品不存在,添加新项
_items.add(CartItem(product: product));
}
// 通知监听者状态已改变
notifyListeners();
}
// 从购物车移除商品
void removeItem(String productId) {
final index = _items.indexWhere((item) => item.product.id == productId);
if (index >= 0) {
_items.removeAt(index);
notifyListeners();
}
}
// 清空购物车
void clear() {
_items.clear();
notifyListeners();
}
// 更新商品数量
void updateQuantity(String productId, int newQuantity) {
final index = _items.indexWhere((item) => item.product.id == productId);
if (index >= 0) {
if (newQuantity <= 0) {
_items.removeAt(index);
} else {
_items[index] = _items[index].copyWith(quantity: newQuantity);
}
notifyListeners();
}
}
// 检查商品是否在购物车中
bool contains(String productId) {
return _items.any((item) => item.product.id == productId);
}
// 获取指定商品的数量
int getQuantity(String productId) {
final item = _items.firstWhere(
(item) => item.product.id == productId,
orElse: () => CartItem(product: Product(id: '', name: '', description: '', price: 0, imageUrl: '')),
);
return item.quantity;
}
}
步骤5:配置Provider
lib/main.dart
dart
复制
下载
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'providers/cart_provider.dart';
import 'screens/product_list_screen.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
// 提供购物车状态
ChangeNotifierProvider(create: (context) => CartProvider()),
],
child: MaterialApp(
title: 'Provider购物车示例',
theme: ThemeData(
primarySwatch: Colors.blue,
useMaterial3: true,
),
home: const ProductListScreen(),
debugShowCheckedModeBanner: false,
),
);
}
}
步骤6:创建商品列表页面
lib/screens/product_list_screen.dart
dart
复制
下载
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../models/product.dart';
import '../providers/cart_provider.dart';
import 'cart_screen.dart';
class ProductListScreen extends StatelessWidget {
const ProductListScreen({super.key});
// 模拟商品数据
final List<Product> _products = const [
Product(
id: '1',
name: '无线蓝牙耳机',
description: '高品质音效,超长续航',
price: 299.0,
imageUrl: 'assets/headphones.jpg',
),
Product(
id: '2',
name: '智能手机',
description: '最新款旗舰手机',
price: 3999.0,
imageUrl: 'assets/phone.jpg',
),
Product(
id: '3',
name: '智能手表',
description: '健康监测,运动助手',
price: 899.0,
imageUrl: 'assets/watch.jpg',
),
Product(
id: '4',
name: '笔记本电脑',
description: '轻薄便携,性能强劲',
price: 5999.0,
imageUrl: 'assets/laptop.jpg',
),
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('商品列表'),
actions: [
// 购物车图标,显示商品数量
Consumer<CartProvider>(
builder: (context, cart, child) {
return Stack(
children: [
IconButton(
icon: const Icon(Icons.shopping_cart),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const CartScreen(),
),
);
},
),
if (cart.totalItems > 0)
Positioned(
right: 8,
top: 8,
child: Container(
padding: const EdgeInsets.all(2),
decoration: const BoxDecoration(
color: Colors.red,
shape: BoxShape.circle,
),
constraints: const BoxConstraints(
minWidth: 16,
minHeight: 16,
),
child: Text(
'${cart.totalItems}',
style: const TextStyle(
color: Colors.white,
fontSize: 10,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
),
),
],
);
},
),
],
),
body: ListView.builder(
itemCount: _products.length,
itemBuilder: (context, index) {
final product = _products[index];
return ProductItem(product: product);
},
),
);
}
}
class ProductItem extends StatelessWidget {
final Product product;
const ProductItem({super.key, required this.product});
@override
Widget build(BuildContext context) {
// 使用Consumer来监听购物车状态
return Consumer<CartProvider>(
builder: (context, cart, child) {
final isInCart = cart.contains(product.id);
final quantityInCart = cart.getQuantity(product.id);
return Card(
margin: const EdgeInsets.all(8),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
children: [
// 商品图片
Container(
width: 80,
height: 80,
decoration: BoxDecoration(
color: Colors.grey[200],
borderRadius: BorderRadius.circular(8),
),
child: const Icon(Icons.shopping_bag, color: Colors.grey),
),
const SizedBox(width: 16),
// 商品信息
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
product.name,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
const SizedBox(height: 4),
Text(
product.description,
style: TextStyle(
color: Colors.grey[600],
fontSize: 14,
),
),
const SizedBox(height: 8),
Text(
'¥${product.price.toStringAsFixed(2)}',
style: const TextStyle(
color: Colors.red,
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
],
),
),
// 购物车操作按钮
Column(
children: [
if (isInCart) ...[
Text(
'已添加: $quantityInCart',
style: const TextStyle(
color: Colors.green,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Row(
children: [
IconButton(
icon: const Icon(Icons.remove),
onPressed: () {
cart.updateQuantity(product.id, quantityInCart - 1);
},
style: IconButton.styleFrom(
backgroundColor: Colors.grey[200],
),
),
const SizedBox(width: 8),
IconButton(
icon: const Icon(Icons.add),
onPressed: () {
cart.updateQuantity(product.id, quantityInCart + 1);
},
style: IconButton.styleFrom(
backgroundColor: Colors.grey[200],
),
),
],
),
] else
ElevatedButton.icon(
onPressed: () {
cart.addItem(product);
},
icon: const Icon(Icons.add_shopping_cart),
label: const Text('加入购物车'),
),
],
),
],
),
),
);
},
);
}
}
步骤7:创建购物车页面
lib/screens/cart_screen.dart
dart
复制
下载
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/cart_provider.dart';
class CartScreen extends StatelessWidget {
const CartScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('购物车'),
),
body: Consumer<CartProvider>(
builder: (context, cart, child) {
if (cart.items.isEmpty) {
return const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.shopping_cart_outlined, size: 64, color: Colors.grey),
SizedBox(height: 16),
Text(
'购物车是空的',
style: TextStyle(fontSize: 18, color: Colors.grey),
),
],
),
);
}
return Column(
children: [
// 商品列表
Expanded(
child: ListView.builder(
itemCount: cart.items.length,
itemBuilder: (context, index) {
final item = cart.items[index];
return CartItemWidget(item: item);
},
),
),
// 底部汇总栏
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
border: Border.top: BorderSide(color: Colors.grey.shade300),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.2),
blurRadius: 8,
offset: const Offset(0, -2),
),
],
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'共 ${cart.totalItems} 件商品',
style: const TextStyle(fontSize: 14),
),
Text(
'合计: ¥${cart.totalAmount.toStringAsFixed(2)}',
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.red,
),
),
],
),
ElevatedButton(
onPressed: () {
_showCheckoutDialog(context, cart);
},
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(
horizontal: 32,
vertical: 16,
),
),
child: const Text('立即结算'),
),
],
),
),
],
);
},
),
);
}
void _showCheckoutDialog(BuildContext context, CartProvider cart) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('确认订单'),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('商品数量: ${cart.totalItems}'),
Text('订单金额: ¥${cart.totalAmount.toStringAsFixed(2)}'),
const SizedBox(height: 16),
const Text('确定要结算吗?'),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('取消'),
),
ElevatedButton(
onPressed: () {
cart.clear();
Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('订单提交成功!')),
);
},
child: const Text('确认'),
),
],
),
);
}
}
class CartItemWidget extends StatelessWidget {
final CartItem item;
const CartItemWidget({super.key, required this.item});
@override
Widget build(BuildContext context) {
return Card(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
child: ListTile(
leading: Container(
width: 50,
height: 50,
decoration: BoxDecoration(
color: Colors.grey[200],
borderRadius: BorderRadius.circular(8),
),
child: const Icon(Icons.shopping_bag, color: Colors.grey),
),
title: Text(item.product.name),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('¥${item.product.price.toStringAsFixed(2)}'),
const SizedBox(height: 4),
Consumer<CartProvider>(
builder: (context, cart, child) {
return Row(
children: [
IconButton(
icon: const Icon(Icons.remove, size: 18),
onPressed: () {
cart.updateQuantity(item.product.id, item.quantity - 1);
},
),
Text('${item.quantity}'),
IconButton(
icon: const Icon(Icons.add, size: 18),
onPressed: () {
cart.updateQuantity(item.product.id, item.quantity + 1);
},
),
],
);
},
),
],
),
trailing: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'¥${item.totalPrice.toStringAsFixed(2)}',
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
Consumer<CartProvider>(
builder: (context, cart, child) {
return IconButton(
icon: const Icon(Icons.delete, color: Colors.red),
onPressed: () {
cart.removeItem(item.product.id);
},
);
},
),
],
),
),
);
}
}
四、Provider 的多种使用方式
4.1 Consumer vs Provider.of
dart
复制
下载
// 方式1:使用Consumer(推荐)
Consumer<CartProvider>(
builder: (context, cart, child) {
return Text('商品数量: ${cart.totalItems}');
},
)
// 方式2:使用Provider.of(在build方法中)
Widget build(BuildContext context) {
final cart = Provider.of<CartProvider>(context);
return Text('商品数量: ${cart.totalItems}');
}
// 方式3:使用Provider.of(在方法中,不监听变化)
void someMethod(BuildContext context) {
final cart = Provider.of<CartProvider>(context, listen: false);
cart.addItem(product);
}
4.2 Selector:精确重建
dart
复制
下载
// 只有totalItems改变时才重建,性能更好
Selector<CartProvider, int>(
selector: (context, cart) => cart.totalItems,
builder: (context, totalItems, child) {
return Text('商品数量: $totalItems');
},
)
五、最佳实践
5.1 状态分类
-
使用 Provider 管理共享状态(用户信息、购物车、主题等)
-
使用 setState 管理局部状态(动画、表单输入等)
5.2 性能优化
-
使用 Selector 替代 Consumer 进行精确重建
-
将不变的Widget提取到 child 参数中
-
避免在build方法中创建新的对象
5.3 代码组织
-
按功能模块组织Provider
-
使用不可变数据模型
-
为复杂业务逻辑创建独立的Service类
结语
恭喜!通过本讲的学习,你已经掌握了Provider这一强大的状态管理工具。你现在可以:
-
理解为什么需要状态管理
-
使用ChangeNotifier创建状态类
-
使用Provider在Widget树中提供状态
-
使用Consumer和Selector消费状态
-
构建复杂的跨组件状态共享应用
Provider让状态管理变得简单而优雅,是中小型Flutter应用的理想选择。
在下一讲中,我们将学习如何与后端API通信,使用http包进行网络请求,让你的应用能够获取和提交真实的数据。
更多推荐



所有评论(0)