在 Flutter 应用开发中,随着应用规模的扩大,组件之间的状态共享和通信会变得越来越复杂。当多个组件需要访问和修改同一状态时,传统的通过构造函数传递参数的方式会导致代码冗余、耦合度高且难以维护。本节课将介绍 Flutter 中常用的状态管理方案 ------Provider,它基于 InheritedWidget 实现,能够简洁高效地解决跨组件状态共享问题。
一、为什么需要状态管理
在 Flutter 中,所有 UI 都是由 Widget 构成的,而 Widget 是不可变的,状态(State)则是 Widget 中可以变化的数据。当应用简单时,我们可以通过 setState()
管理单个组件的状态,但当状态需要在多个组件之间共享时,就会面临以下问题:
- 跨组件通信困难:深层嵌套的子组件需要访问父组件的状态时,必须通过层层传递参数,形成 "prop drilling" 问题
- 状态同步复杂:多个组件依赖同一状态时,手动同步状态会导致代码逻辑混乱
- 代码可维护性差:状态分散在各个组件中,修改和调试变得困难
- 性能问题:不合理的状态管理会导致不必要的组件重建,影响应用性能
举个例子,一个电商应用中的购物车状态可能需要在商品列表、购物车页面、结算页面等多个地方访问和修改。如果使用传统方式,需要在各个页面之间传递购物车数据,当购物车发生变化时,还要手动通知所有相关页面更新,这显然是低效且容易出错的。
状态管理方案正是为了解决这些问题而诞生的,Provider 是其中最简单易用且官方推荐的方案之一。
二、Provider 基本概念与核心类
Provider 是基于 Flutter 原生 InheritedWidget
实现的状态管理库,它的核心思想是将共享状态抽离到一个独立的类中,然后通过一个 Provider 组件在 Widget 树中提供这个状态,最后在需要使用该状态的子组件中进行消费。
1. 核心类介绍
- ChangeNotifier :一个实现了观察者模式的类,当状态发生变化时,调用
notifyListeners()
方法通知所有监听者更新 - ChangeNotifierProvider :一个 Widget,用于在 Widget 树中提供
ChangeNotifier
实例,使其子树中的 Widget 可以访问该实例 - Consumer :用于在子 Widget 中获取并监听
ChangeNotifier
实例,当状态变化时会重建自身 - Provider.of :另一种获取
ChangeNotifier
实例的方法,可以选择是否监听状态变化
2. 安装 Provider
在使用 Provider 之前,需要在 pubspec.yaml
中添加依赖:
yaml
dependencies:
flutter:
sdk: flutter
provider: ^6.1.5 # 请使用最新版本
然后运行 flutter pub get
安装依赖。
三、Provider 基本使用流程
使用 Provider 管理状态通常遵循以下步骤:
- 创建一个继承自
ChangeNotifier
的状态类,封装需要共享的状态和修改状态的方法 - 使用
ChangeNotifierProvider
在 Widget 树的适当位置提供该状态实例 - 在需要使用状态的子 Widget 中,通过
Consumer
或Provider.of
获取状态并使用
下面通过一个简单的计数器示例来演示基本用法:
1. 创建状态类
dart
import 'package:flutter/foundation.dart';
// 继承 ChangeNotifier
class Counter with ChangeNotifier {
int _count = 0;
// 提供获取状态的方法
int get count => _count;
// 提供修改状态的方法
void increment() {
_count++;
// 通知所有监听者
notifyListeners();
}
void decrement() {
_count--;
notifyListeners();
}
void reset() {
_count = 0;
notifyListeners();
}
}
2. 提供状态
在 Widget 树中使用 ChangeNotifierProvider
提供状态,通常放在应用的根部:
dart
import 'package:provider/provider.dart';
void main() {
runApp(
// 提供 Counter 实例
ChangeNotifierProvider(
create: (context) => Counter(),
child: const MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Provider Demo',
home: const CounterPage(),
);
}
}
3. 消费状态
在子 Widget 中通过 Consumer
或 Provider.of
获取并使用状态:
使用 Consumer
dart
import 'package:provider/provider.dart';
class CounterPage extends StatelessWidget {
const CounterPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Counter Demo')),
body: Center(
// 使用 Consumer 获取 Counter 实例
child: Consumer<Counter>(
builder: (context, counter, child) {
// builder 方法会在状态变化时重建
return Text(
'Current count: ${counter.count}',
style: const TextStyle(fontSize: 24),
);
},
),
),
floatingActionButton: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
onPressed: () {
// 使用 Provider.of 获取实例(不监听变化)
Provider.of<Counter>(context, listen: false).decrement();
},
child: const Icon(Icons.remove),
),
const SizedBox(width: 10),
FloatingActionButton(
onPressed: () {
Provider.of<Counter>(context, listen: false).increment();
},
child: const Icon(Icons.add),
),
],
),
);
}
}
Consumer 性能优化
Consumer
的 child
参数可以用于优化性能,避免不必要的重建:
dart
Consumer<Counter>(
builder: (context, counter, child) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 这个 Text 会在状态变化时重建
Text('Count: ${counter.count}'),
// 这个子组件不会重建,因为它被提取到了 child 参数中
child!,
],
);
},
// 这个子组件只会构建一次
child: const Text('This is a static text'),
)
使用 Provider.of
Provider.of<T>(context)
会获取最近的 T
类型的 Provider,并在状态变化时重建当前 Widget:
dart
// 监听状态变化,状态变化时会重建当前 Widget
final counter = Provider.of<Counter>(context);
// 不监听状态变化,通常用于修改状态的场景
final counter = Provider.of<Counter>(context, listen: false);
使用示例:
dart
class CounterDisplay extends StatelessWidget {
const CounterDisplay({super.key});
@override
Widget build(BuildContext context) {
// 获取 Counter 实例并监听变化
final counter = Provider.of<Counter>(context);
return Text(
'Count: ${counter.count}',
style: const TextStyle(fontSize: 24),
);
}
}
class CounterActions extends StatelessWidget {
const CounterActions({super.key});
@override
Widget build(BuildContext context) {
return FloatingActionButton(
onPressed: () {
// 获取 Counter 实例但不监听变化
Provider.of<Counter>(context, listen: false).increment();
},
child: const Icon(Icons.add),
);
}
}
四、多个状态管理
当应用中有多个独立的状态需要管理时,可以使用 MultiProvider
来组织多个 Provider:
dart
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => Counter()),
ChangeNotifierProvider(create: (context) => ThemeProvider()),
ChangeNotifierProvider(create: (context) => UserProvider()),
],
child: const MyApp(),
),
);
}
这样,在子组件中可以分别获取不同的状态:
dart
// 获取计数器状态
final counter = Provider.of<Counter>(context);
// 获取主题状态
final themeProvider = Provider.of<ThemeProvider>(context);
五、实例:用 Provider 管理购物车状态
下面通过一个完整的购物车示例,展示如何使用 Provider 管理复杂状态:
1. 定义数据模型
dart
// 商品模型
class Product {
final String id;
final String name;
final double price;
final String imageUrl;
Product({
required this.id,
required this.name,
required this.price,
required this.imageUrl,
});
}
// 购物车项模型
class CartItem {
final String id;
final String productId;
final String name;
final int quantity;
final double price;
CartItem({
required this.id,
required this.productId,
required this.name,
required this.quantity,
required this.price,
});
// 计算小计
double get totalPrice => price * quantity;
}
2. 创建购物车状态管理类
dart
import 'package:flutter/foundation.dart';
import 'dart:collection';
class Cart with ChangeNotifier {
// 存储购物车项,键为商品ID
final Map<String, CartItem> _items = {};
// 提供不可修改的购物车项视图
UnmodifiableMapView<String, CartItem> get items => UnmodifiableMapView(_items);
// 获取购物车项数量
int get itemCount => _items.length;
// 计算购物车总价
double get totalAmount {
var total = 0.0;
_items.forEach((key, cartItem) {
total += cartItem.price * cartItem.quantity;
});
return total;
}
// 添加商品到购物车
void addItem(Product product) {
if (_items.containsKey(product.id)) {
// 如果商品已在购物车中,增加数量
_items.update(
product.id,
(existingItem) => CartItem(
id: existingItem.id,
productId: existingItem.productId,
name: existingItem.name,
quantity: existingItem.quantity + 1,
price: existingItem.price,
),
);
} else {
// 如果商品不在购物车中,添加新项
_items.putIfAbsent(
product.id,
() => CartItem(
id: DateTime.now().toString(),
productId: product.id,
name: product.name,
quantity: 1,
price: product.price,
),
);
}
notifyListeners();
}
// 从购物车中移除商品
void removeItem(String productId) {
_items.remove(productId);
notifyListeners();
}
// 减少购物车中商品的数量
void removeSingleItem(String productId) {
if (!_items.containsKey(productId)) {
return;
}
if (_items[productId]!.quantity > 1) {
_items.update(
productId,
(existingItem) => CartItem(
id: existingItem.id,
productId: existingItem.productId,
name: existingItem.name,
quantity: existingItem.quantity - 1,
price: existingItem.price,
),
);
} else {
_items.remove(productId);
}
notifyListeners();
}
// 清空购物车
void clear() {
_items.clear();
notifyListeners();
}
}
3. 创建商品列表状态类
dart
class Products with ChangeNotifier {
final List<Product> _items = [
Product(
id: 'p1',
name: 'Red Shirt',
price: 29.99,
imageUrl: 'https://picsum.photos/200/300?random=1',
),
Product(
id: 'p2',
name: 'Trousers',
price: 59.99,
imageUrl: 'https://picsum.photos/200/300?random=2',
),
Product(
id: 'p3',
name: 'Yellow Scarf',
price: 19.99,
imageUrl: 'https://picsum.photos/200/300?random=3',
),
Product(
id: 'p4',
name: 'A Shoes',
price: 99.99,
imageUrl: 'https://picsum.photos/200/300?random=4',
),
];
// 获取商品列表
List<Product> get items => [..._items];
}
4. 在应用中提供状态
dart
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (ctx) => Products()),
ChangeNotifierProvider(create: (ctx) => Cart()),
],
child: const MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Shopping Cart Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: const ProductsOverviewScreen(),
routes: {
CartScreen.routeName: (ctx) => const CartScreen(),
},
);
}
}
5. 实现商品列表页面
dart
class ProductsOverviewScreen extends StatelessWidget {
const ProductsOverviewScreen({super.key});
@override
Widget build(BuildContext context) {
// 获取商品列表
final productsData = Provider.of<Products>(context);
final products = productsData.items;
return Scaffold(
appBar: AppBar(
title: const Text('My Shop'),
actions: [
// 购物车图标,显示商品数量
Consumer<Cart>(
builder: (ctx, cart, ch) =>
Badge(label: Text(cart.itemCount.toString()), child: ch!),
child: IconButton(
icon: const Icon(Icons.shopping_cart),
onPressed: () {
Navigator.of(context).pushNamed(CartScreen.routeName);
},
),
),
],
),
body: GridView.builder(
padding: const EdgeInsets.all(10.0),
itemCount: products.length,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 3 / 2,
crossAxisSpacing: 10,
mainAxisSpacing: 10,
),
itemBuilder: (ctx, i) => ProductItem(products[i]),
),
);
}
}
class ProductItem extends StatelessWidget {
final Product product;
const ProductItem(this.product, {super.key});
@override
Widget build(BuildContext context) {
return ClipRRect(
borderRadius: BorderRadius.circular(10),
child: GridTile(
footer: GridTileBar(
backgroundColor: Colors.black87,
title: Text(product.name, textAlign: TextAlign.center),
trailing: IconButton(
icon: const Icon(Icons.shopping_cart),
onPressed: () {
// 将商品添加到购物车
Provider.of<Cart>(context, listen: false).addItem(product);
// 显示添加成功提示
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text('Added item to cart!'),
duration: const Duration(seconds: 2),
action: SnackBarAction(
label: 'UNDO',
onPressed: () {
Provider.of<Cart>(
context,
listen: false,
).removeSingleItem(product.id);
},
),
),
);
},
color: Theme.of(context).colorScheme.secondary,
),
),
child: Image.network(product.imageUrl, fit: BoxFit.cover),
),
);
}
}
6. 实现购物车页面
dart
class CartScreen extends StatelessWidget {
static const routeName = '/cart';
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: () {
// 这里可以添加结算逻辑
},
child: const Text('ORDER NOW'),
)
],
),
),
),
const SizedBox(height: 10),
Expanded(
child: ListView.builder(
itemCount: cart.items.length,
itemBuilder: (ctx, i) {
final cartItem = cart.items.values.toList()[i];
return ListTile(
leading: CircleAvatar(
child: FittedBox(
child: Text('$${cartItem.price}'),
),
),
title: Text(cartItem.name),
subtitle: Text('Total: $${(cartItem.price * cartItem.quantity).toStringAsFixed(2)}'),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Icons.remove),
onPressed: () {
cart.removeSingleItem(cartItem.productId);
},
),
Text('${cartItem.quantity}'),
IconButton(
icon: const Icon(Icons.add),
onPressed: () {
// 这里可以添加增加商品数量的逻辑
// 简化示例中暂不实现
},
),
],
),
);
},
),
)
],
),
);
}
}
六、Provider 高级用法
1. Selector 优化重建
Selector
是 Consumer
的进阶版本,它可以根据指定的条件决定是否重建,进一步优化性能:
dart
Selector<Counter, int>(
// 选择需要监听的状态
selector: (context, counter) => counter.count,
// 只有当选中的状态变化时才会重建
builder: (context, count, child) {
return Text('Count: $count');
},
)
Selector
接收两个泛型参数:第一个是 ChangeNotifier
类型,第二个是需要监听的状态类型。只有当 selector
方法返回的值发生变化时,builder
才会被调用。
2. 状态持久化
结合 shared_preferences 或 hive 等本地存储库,可以实现状态的持久化:
dart
class Counter with ChangeNotifier {
int _count = 0;
final SharedPreferences _prefs;
Counter(this._prefs) {
// 从本地存储加载状态
_count = _prefs.getInt('count') ?? 0;
}
int get count => _count;
void increment() {
_count++;
// 保存状态到本地存储
_prefs.setInt('count', _count);
notifyListeners();
}
}
// 提供带持久化的状态
ChangeNotifierProvider(
create: (context) => Counter(SharedPreferences.getInstance()),
child: MyApp(),
)
3. 状态封装与业务逻辑分离
对于复杂应用,建议将业务逻辑与状态管理分离,保持 ChangeNotifier
的简洁:
dart
// 业务逻辑层
class CartService {
Future<void> addToCart(Product product) async {
// 处理添加到购物车的业务逻辑,如网络请求等
await Future.delayed(const Duration(milliseconds: 300));
}
}
// 状态管理层
class Cart with ChangeNotifier {
final CartService _cartService;
final List<CartItem> _items = [];
Cart(this._cartService);
List<CartItem> get items => [..._items];
Future<void> addItem(Product product) async {
try {
// 调用业务逻辑
await _cartService.addToCart(product);
// 更新状态
_items.add(CartItem(...));
notifyListeners();
} catch (e) {
// 处理错误
rethrow;
}
}
}
七、Provider 使用最佳实践
- 状态粒度适中:避免创建过大的状态类,应根据功能模块拆分状态
- 最小重建原则 :使用
Consumer
和Selector
限制重建范围,避免不必要的重建 - 单一职责:每个状态类应只负责管理相关的一组状态,遵循单一职责原则
- 不可变数据:在状态类中,对于复杂数据结构,建议使用不可变对象,通过替换整个对象来更新状态
- 避免在 build 方法中创建状态 :确保
ChangeNotifier
实例的创建在create
方法中,而不是在 build 方法中 - 清理资源 :如果状态类中使用了需要手动释放的资源(如定时器),应在
dispose
方法中清理
dart
class TimerModel with ChangeNotifier {
late Timer _timer;
int _seconds = 0;
TimerModel() {
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
_seconds++;
notifyListeners();
});
}
int get seconds => _seconds;
@override
void dispose() {
// 清理资源
_timer.cancel();
super.dispose();
}
}