第12讲:入门级状态管理方案 - Provider详解
使用Provider优雅地管理跨组件共享状态,告别繁琐的回调传递。
你好,欢迎回到《Flutter入门到精通》专栏。在上一讲中,我们深入学习了如何使用setState管理局部状态。但当状态需要在多个Widget之间共享时,setState就显得力不从心了。今天,我们将学习Flutter社区最受欢迎的状态管理方案之一——Provider。
一、为什么需要状态管理?
1.1 setState 的局限性回顾
考虑这样一个场景:用户信息需要在多个页面共享
dart
复制
下载
// 使用setState实现,需要层层传递回调 - 非常繁琐!
class MyApp extends StatefulWidget {const MyApp({super.key});@overrideState<MyApp> createState() => _MyAppState();
}class _MyAppState extends State<MyApp> {User _user = User(name: '张三', email: 'zhangsan@example.com');void _updateUser(User newUser) {setState(() {_user = newUser;});}@overrideWidget 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: flutterprovider: ^6.1.1 # 添加Provider依赖dev_dependencies:flutter_test:sdk: flutterflutter_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});@overrideWidget 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',),];@overrideWidget 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});@overrideWidget 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],),),],),] elseElevatedButton.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});@overrideWidget 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});@overrideWidget 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包进行网络请求,让你的应用能够获取和提交真实的数据。
