StateProvider
StateProvider是一个公开了一种修改其状态的方法的provider。 它是 StateNotifierProvider 的简化版,旨在避免为非常简单的用例编写 StateNotifier 类。
StateProvider
的存在主要是为了允许用户界面对简单的变量进行修改。
所以StateProvider
的状态通常为:
- 枚举类型,例如筛选器类型
- 一段字符串(String),通常是输入框的原始内容
- 用于复选框的布尔类型
- 用于分页或年龄表单字段的数字
你不应该使用 StateProvider
如果:
- 你的状态需要验证逻辑
- 你的状态是一个复杂的对象 (比如自定义的类, 集合……)
- 修改状态的逻辑比简单的
count++
更复杂
对于更复杂的情况,可以考虑使用 StateNotifierProvider ,并创建一个 StateNotifier 类。 虽然最初的样板文件会有点大, 但有一个自定义的 StateNotifier 类对于项目的长期可维护性是至关重要的, 因为它将状态的业务逻辑集中在了一个地方。
使用示例:使用下拉菜单更改筛选类型
StateProvider
的一个真实的用例是管理简单表单组件的状态,比如下拉菜单/输入框/复选框。
特别来说,我们将看到如何使用 StateProvider
实现一个下拉菜单,
该下拉菜单允许更改产品列表的排序方式。
为了简单起见,我们将在应用中直接构建将获得的产品列表,如下所示:
class Product {
Product({required this.name, required this.price});
final String name;
final double price;
}
final _products = [
Product(name: 'iPhone', price: 999),
Product(name: 'cookie', price: 2),
Product(name: 'ps5', price: 500),
];
final productsProvider = Provider<List<Product>>((ref) {
return _products;
});
在真实的应用程序中,我们通常会使用 FutureProvider 通过网络请求来获得该列表。
然后,可以在用户界面上通过下面的操作来显示产品列表:
Widget build(BuildContext context, WidgetRef ref) {
final products = ref.watch(productsProvider);
return Scaffold(
body: ListView.builder(
itemCount: products.length,
itemBuilder: (context, index) {
final product = products[index];
return ListTile(
title: Text(product.name),
subtitle: Text('${product.price} \$'),
);
},
),
);
}
现在我们已经完成了基础,我们可以添加一个下拉菜单, 它将按价格或名称过滤我们的产品。 为此,我们将使用DropDownButton。
// An enum representing the filter type
enum ProductSortType {
name,
price,
}
Widget build(BuildContext context, WidgetRef ref) {
final products = ref.watch(productsProvider);
return Scaffold(
appBar: AppBar(
title: const Text('Products'),
actions: [
DropdownButton<ProductSortType>(
value: ProductSortType.price,
onChanged: (value) {},
items: const [
DropdownMenuItem(
value: ProductSortType.name,
child: Icon(Icons.sort_by_alpha),
),
DropdownMenuItem(
value: ProductSortType.price,
child: Icon(Icons.sort),
),
],
),
],
),
body: ListView.builder(
// ... /* SKIP */
itemBuilder: (c, i) => Container(), /* SKIP END */
),
);
}
现在我们有了一个下拉列表,
让我们创建一个 StateProvider
并将下拉菜单的状态与我们的provider同步。
首先,让我们创建一个 StateProvider
:
final productSortTypeProvider = StateProvider<ProductSortType>(
// We return the default sort type, here name.
(ref) => ProductSortType.name,
);
然后,我们可以通过下面的操作将这个provider与我们的下拉菜单连接起来:
DropdownButton<ProductSortType>(
// When the sort type changes, this will rebuild the dropdown
// to update the icon shown.
value: ref.watch(productSortTypeProvider),
// When the user interacts with the dropdown, we update the provider state.
onChanged: (value) =>
ref.read(productSortTypeProvider.notifier).state = value!,
items: [
// ...
],
),
有了这些,我们现在应该能够更改筛选的类型。
不过它对产品列表没有影响!
现在是最后一部分:更新我们的 productsProvider
以对产品列表进行排序。
实现这一点的关键是使用 ref.watch,
让我们的 productsProvider
获得排序类型,
并在排序类型更改时重新计算产品列表。
代码实现会是:
final productsProvider = Provider<List<Product>>((ref) {
final sortType = ref.watch(productSortTypeProvider);
switch (sortType) {
case ProductSortType.name:
return _products.sorted((a, b) => a.name.compareTo(b.name));
case ProductSortType.price:
return _products.sorted((a, b) => a.price.compareTo(b.price));
}
});
就是这样!这个变改足以让用户界面在排序类型更改时自动重绘产品列表。
下面是在Dartpad上完整的例子:
如何在不读取provider两次的情况下根据之前的值更新状态
有时你希望根据之前的值更新 StateProvider
的状态。
自然而然,你可能会这样写:
final counterProvider = StateProvider<int>((ref) => 0);
class HomeView extends ConsumerWidget {
const HomeView({super.key});
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
// We're updating the state from the previous value, we ended-up reading
// the provider twice!
ref.read(counterProvider.notifier).state = ref.read(counterProvider.notifier).state + 1;
},
),
);
}
}
虽然这段代码没有什么特别的错误,但是语法上着实有点不太方便。
为了更方便地使用,我们可以使用 update
函数。
这个函数将接受一个回调函数,该回调函数将接收当前状态并返回新状态。
我们可以使用它来重构之前的代码:
final counterProvider = StateProvider<int>((ref) => 0);
class HomeView extends ConsumerWidget {
const HomeView({super.key});
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
ref.read(counterProvider.notifier).update((state) => state + 1);
},
),
);
}
}
这样就实现了相同的效果,而且使语法更好一些。