Flutter Provider 详细讲解与实战
Provider 是 Flutter 中最流行的状态管理解决方案之一,它是对 InheritedWidget 的封装,使得状态管理更加简单和高效。下面我将详细介绍 Provider 的使用方法,并通过实战示例来演示其应用。
1. Provider 基本概念
1.1 为什么需要 Provider
在 Flutter 中,Widget 树是层级结构的,当需要在不同层级的 Widget 之间共享数据时,如果使用传统的构造函数传递,会导致代码非常繁琐。Provider 提供了一种优雅的方式来在 Widget 树中共享和管理状态。
1.2 Provider 的核心思想
- 状态提升:将状态提升到共同的祖先 Widget
- 依赖注入:通过 Provider 将状态注入到 Widget 树中
- 按需获取:任何子 Widget 都可以根据需要获取状态
2. 添加 Provider 依赖
在 pubspec.yaml
中添加依赖:
yaml
dependencies:
flutter:
sdk: flutter
provider: ^6.0.0
然后运行 flutter pub get
安装依赖。
3. Provider 的基本使用
3.1 创建数据模型
首先,我们需要创建一个可观察的数据模型,通常继承自 ChangeNotifier
:
dart
import 'package:flutter/foundation.dart';
class Counter with ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners(); // 通知监听者数据已改变
}
}
3.2 在顶层提供数据
在应用的顶层 Widget 使用 ChangeNotifierProvider
提供数据:
dart
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => Counter(),
child: const MyApp(),
),
);
}
3.3 在子 Widget 中获取数据
有两种方式获取 Provider 中的数据:
方式一:使用 Provider.of
dart
class CounterDisplay extends StatelessWidget {
const CounterDisplay({super.key});
@override
Widget build(BuildContext context) {
final counter = Provider.of<Counter>(context);
return Text('Count: ${counter.count}');
}
}
方式二:使用 Consumer
dart
class CounterDisplay extends StatelessWidget {
const CounterDisplay({super.key});
@override
Widget build(BuildContext context) {
return Consumer<Counter>(
builder: (context, counter, child) {
return Text('Count: ${counter.count}');
},
);
}
}
3.4 更新数据
dart
class CounterButton extends StatelessWidget {
const CounterButton({super.key});
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () {
Provider.of<Counter>(context, listen: false).increment();
},
child: const Text('Increment'),
);
}
}
注意:当只需要调用方法而不需要监听数据变化时,设置 listen: false
可以提高性能。
4. Provider 实战示例:购物车应用
让我们通过一个购物车应用来演示 Provider 的实际使用。
4.1 数据模型
dart
class Product {
final String id;
final String name;
final double price;
Product({required this.id, required this.name, required this.price});
}
class CartItem {
final Product product;
int quantity;
CartItem({required this.product, this.quantity = 1});
}
class Cart with ChangeNotifier {
final List<CartItem> _items = [];
List<CartItem> get items => _items;
int get itemCount => _items.fold(0, (sum, item) => sum + item.quantity);
double get totalAmount => _items.fold(0, (sum, item) => sum + item.product.price * item.quantity);
void addItem(Product product) {
final index = _items.indexWhere((item) => item.product.id == product.id);
if (index >= 0) {
_items[index].quantity++;
} else {
_items.add(CartItem(product: product));
}
notifyListeners();
}
void removeItem(String productId) {
final index = _items.indexWhere((item) => item.product.id == productId);
if (index >= 0) {
if (_items[index].quantity > 1) {
_items[index].quantity--;
} else {
_items.removeAt(index);
}
notifyListeners();
}
}
void clear() {
_items.clear();
notifyListeners();
}
}
4.2 应用结构
dart
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (ctx) => Cart()),
],
child: const MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Shopping App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const ProductListScreen(),
routes: {
'/cart': (ctx) => const CartScreen(),
},
);
}
}
4.3 商品列表页面
dart
class ProductListScreen extends StatelessWidget {
const ProductListScreen({super.key});
@override
Widget build(BuildContext context) {
final cart = Provider.of<Cart>(context, listen: false);
return Scaffold(
appBar: AppBar(
title: const Text('Products'),
actions: [
IconButton(
icon: const Icon(Icons.shopping_cart),
onPressed: () => Navigator.pushNamed(context, '/cart'),
),
Badge(
child: const Icon(Icons.shopping_cart),
value: cart.itemCount.toString(),
),
],
),
body: ListView.builder(
itemCount: dummyProducts.length,
itemBuilder: (ctx, i) => ListTile(
title: Text(dummyProducts[i].name),
subtitle: Text('\$${dummyProducts[i].price}'),
trailing: IconButton(
icon: const Icon(Icons.add_shopping_cart),
onPressed: () {
cart.addItem(dummyProducts[i]);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('${dummyProducts[i].name} added to cart!'),
duration: const Duration(seconds: 2),
),
);
},
),
),
),
);
}
}
4.4 购物车页面
dart
class CartScreen extends StatelessWidget {
const CartScreen({super.key});
@override
Widget build(BuildContext context) {
final cart = Provider.of<Cart>(context);
return Scaffold(
appBar: AppBar(
title: const Text('Your Cart'),
),
body: Column(
children: [
Card(
margin: const EdgeInsets.all(15),
child: Padding(
padding: const EdgeInsets.all(8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('Total', style: TextStyle(fontSize: 20)),
const Spacer(),
Chip(
label: Text(
'\$${cart.totalAmount.toStringAsFixed(2)}',
style: TextStyle(
color: Theme.of(context).primaryTextTheme.titleLarge?.color,
),
),
backgroundColor: Theme.of(context).primaryColor,
),
TextButton(
onPressed: () {
cart.clear();
},
child: const Text('ORDER NOW'),
),
],
),
),
),
Expanded(
child: ListView.builder(
itemCount: cart.items.length,
itemBuilder: (ctx, i) => Dismissible(
key: ValueKey(cart.items[i].product.id),
background: Container(
color: Theme.of(context).errorColor,
alignment: Alignment.centerRight,
padding: const EdgeInsets.only(right: 20),
margin: const EdgeInsets.symmetric(
horizontal: 15,
vertical: 4,
),
child: const Icon(
Icons.delete,
color: Colors.white,
size: 40,
),
),
direction: DismissDirection.endToStart,
onDismissed: (direction) {
cart.removeItem(cart.items[i].product.id);
},
child: Card(
margin: const EdgeInsets.symmetric(
horizontal: 15,
vertical: 4,
),
child: Padding(
padding: const EdgeInsets.all(8),
child: ListTile(
leading: CircleAvatar(
child: Padding(
padding: const EdgeInsets.all(5),
child: FittedBox(
child: Text('\$${cart.items[i].product.price}'),
),
),
),
title: Text(cart.items[i].product.name),
subtitle: Text('Total: \$${(cart.items[i].product.price * cart.items[i].quantity).toStringAsFixed(2)}'),
trailing: Text('${cart.items[i].quantity} x'),
),
),
),
),
),
),
],
),
);
}
}
5. Provider 的高级用法
5.1 MultiProvider
当需要提供多个 Provider 时,可以使用 MultiProvider
:
dart
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (ctx) => Auth()),
ChangeNotifierProvider(create: (ctx) => Products()),
ChangeNotifierProvider(create: (ctx) => Cart()),
],
child: const MyApp(),
),
);
}
5.2 ProxyProvider
当某个 Provider 依赖于另一个 Provider 时,可以使用 ProxyProvider
:
dart
MultiProvider(
providers: [
ChangeNotifierProvider(create: (ctx) => Auth()),
ProxyProvider<Auth, Products>(
update: (ctx, auth, previousProducts) => Products(auth.token),
),
],
child: const MyApp(),
)
5.3 Selector
Selector
是 Consumer
的优化版本,它只在特定数据变化时重建:
dart
Selector<Cart, int>(
selector: (ctx, cart) => cart.itemCount,
builder: (ctx, count, child) => Badge(
child: child!,
value: count.toString(),
),
child: IconButton(
icon: const Icon(Icons.shopping_cart),
onPressed: () => Navigator.pushNamed(context, '/cart'),
),
)
6. 最佳实践
- 最小化重建范围 :使用
Consumer
或Selector
时,尽量只包裹需要重建的部分 - 分离业务逻辑和 UI:将业务逻辑放在 ChangeNotifier 类中
- 避免大型 ChangeNotifier:将大的状态拆分为多个小的 ChangeNotifier
- 合理使用 listen :当只需要调用方法时,使用
listen: false
- 考虑使用 immutable 数据:对于复杂状态,考虑使用不可变数据模型
7. 总结
Provider 是 Flutter 中简单而强大的状态管理解决方案,它:
- 基于 InheritedWidget,性能高效
- 提供了多种 Provider 类型满足不同需求
- 具有清晰的关注点分离
- 易于测试和维护
通过本文的讲解和实战示例,你应该已经掌握了 Provider 的核心概念和使用方法。在实际项目中,可以根据需求选择合适的 Provider 类型和组合方式,构建出高效、可维护的 Flutter 应用。