Riverpod的官方文档有多国语言,但是没有汉语,所以个人简单翻译了一版。
官网文档:Riverpod
GitHub:GitHub - rrousselGit/river_pod
Pub:riverpod | Dart Package (flutter-io.cn)
译时版本:riverpod 1.0.3
StateProvider
StateProvider
暴露了改变其状态的方式。 它是 StateNotifierProvider 的简化版, 设计用于很简单的使用场景下避免编写 StateNotifier 类。
StateProvider
的存在主要是允许通过 UI 对 简单 变量的更改。
StateProvider
的状态通常是:
- 枚举,例如过滤类型
- 字符串,通常是文件框(TextField之类)的原始内容
- 逻辑变量,用于复选框
- 数字,用于分页或表单中的年龄
下面的情况,不应该使用 StateProvider
:
- 状态需要验证逻辑
- 状态是复杂的对象(例如自定义类、列表/Map、等)
- 更改状态的逻辑比简单的
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 通过网络请求获取。
然后 UI 会如下显示商品列表:
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。
// 表现过滤类型的枚举
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(
// ...
),
);
}
现在有了下拉框,让我们创建一个 StateProvider
并用 provider 同步下拉框的状态。
首先,创建 StateProvider
:
final productSortTypeProvider = StateProvider<ProductSortType>(
// 我们返回排序类型的默认值,这里是名称。
(ref) => ProductSortType.name,
);
然后,我们可以如下将 provider 和 下拉框连接:
DropdownButton<ProductSortType>(
// 当排序类型改变时,这会重新构建下拉框来改变显示的图标。
value: ref.watch(productSortTypeProvider),
// 当用户和下拉框交互时,我们更新 provider 的状态。
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));
}
});
这就是所有了!当排序类型改变时,对于 UI 自动重新渲染商品列表来说足够了。
Dartpad 上的完整示例:
// 该代码基于 MIT 许可证分发。
// Copyright (c) 2022 Remi Rousselet.
// 原始代码可在 https://github.com/rrousselGit/river_pod 找到。
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
void main() {
runApp(const ProviderScope(child: MyApp()));
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: MyHomePage(),
);
}
}
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),
];
enum ProductSortType {
name,
price,
}
final productSortTypeProvider = StateProvider<ProductSortType>(
// 我们返回排序类型的默认值,这里是名称。
(ref) => ProductSortType.name,
);
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));
}
});
class MyHomePage extends ConsumerWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
final products = ref.watch(productsProvider);
return Scaffold(
appBar: AppBar(
title: const Text('Products'),
actions: [
DropdownButton<ProductSortType>(
// 当排序类型改变时,这会重新构建下拉框来改变显示的图标。
value: ref.watch(productSortTypeProvider),
// 当用户和下拉框交互时,我们更新 provider 的状态。
onChanged: (value) =>
ref.read(productSortTypeProvider.notifier).state = value!,
items: const [
DropdownMenuItem(
value: ProductSortType.name,
child: Icon(Icons.sort_by_alpha),
),
DropdownMenuItem(
value: ProductSortType.price,
child: Icon(Icons.sort),
),
],
),
],
),
body: ListView.builder(
itemCount: products.length,
itemBuilder: (context, index) {
final product = products[index];
return ListTile(
title: Text(product.name),
subtitle: Text('${product.price} \$'),
);
},
),
);
}
}
如何基于前一次的值更新状态时避免读取 provider 两次
有时候,想基于前一次的值更新 StateProvider
的状态。很自然地,你可以会如下去写:
final counterProvider = StateProvider<int>((ref) => 0);
class HomeView extends ConsumerWidget {
const HomeView({Key? key}): super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
// 我们从前一次的值更新状态,这样结束时会读取 provider 两次!
ref.read(counterProvider.notifier).state = ref.read(counterProvider.notifier).state + 1;
},
),
);
}
}
该代码版本也没有什么特别错误,只是语法上有点不方便。
要使语法看上去更好些,可以使用 update
函数。 该函数会接收一个回调函数,该回调函数会接收当前状态然后期望结果是返回新的状态。
我们可以用它来重构前面的代码:
final counterProvider = StateProvider<int>((ref) => 0);
class HomeView extends ConsumerWidget {
const HomeView({Key? key}): super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
ref.read(counterProvider.notifier).update((state) => state + 1);
},
),
);
}
}
该改动会达到同样的效果,也能使语法更好些。
最后
第一章 为什么 Flutter 是跨平台开发的终极之选
- 这是为什么?
- 跨平台开发
- 什么是Flutter
- Flutter特性
- Flutter 构建应用的工具
- 使用 Flutter 构建的热门应用
- 构建 Flutter 应用的成本
第二章 在Windows上搭建Flutter开发环境
- 使用镜像
- 系统要求
- 获取Flutter SDK
- 编辑器设置
- Android设置
- 起步: 配置编辑器
- 起步: 体验
- 体验热重载
第三章 编写您的第一个 Flutter App
- 创建 Flutter app
- 使用外部包(package)
- 添加一个 有状态的部件(Stateful widget)
- 创建一个无限滚动ListView
- 添加交互
- 导航到新页面
- 使用主题更改UI
第四章 Flutter开发环境搭建和调试
- 开发环境的搭建
- 模拟器的安装与调试
- 开发环境的搭建
- 模拟器的安装与调试
第五章 Dart语法篇之基础语法(一)
- 简述
- Hello Dart
- 数据类型
- 变量和常量
- 集合(List、Set、Map)
- 流程控制
- 运算符
- 异常
- 函数
- 总结
第六章 Dart语法篇之集合的使用与源码解析(二)
- List
- Set
- Map
- Queue
- LinkedList
- HashMap
- Map、HashMap、LinkedHashMap、SplayTreeMap区别
- 命名构造函数from和of的区别以及使用建议
第七章 Dart语法篇之集合操作符函数与源码分析(三)
- 简述
- Iterable
- forEach
- map
- any
- every
- where
- firstWhere和singleWhere和lastWhere
- join
- take
- takeWhile
- skip
- skipWhile
- follwedBy
- expand
- reduce
- elementAt
第八章 Dart语法篇之函数的使用(四)
- 简述
- 函数参数
- 匿名函数(闭包,lambda)
- 箭头函数
- 局部函数
- 顶层函数和静态函数
- main函数
- Function函数对象
第九章 Dart语法篇之面向对象基础(五)
- 简述
- 属性访问器(accessor)函数setter和getter
- 面向对象中的变量
- 构造函数
- 抽象方法、抽象类和接口
- 类函数
- 总结
第十章 Dart语法篇之面向对象继承和Mixins(六)
- 简述
- 类的单继承
- 基于Mixins的多继承
- 总结
第十一章 Dart语法篇之类型系统与泛型(七)
- 简述
- 可选类型
- 接口类型
- 泛型
- 类型具体化
- 总结
第十二章 Flutter中的widget
- Flutter页面-基础Widget
- Widget
- StatelessWidget
- State生命周期
- 基础widget
- DefaultTextStyle
- FlutterLogo
- Icon
- Iamge.asset
- CircleAvatar
- FadeInImage
- 按钮
- FlatButton
- OutlineButton
- TextFormField
网友评论