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
- Flutter特性
- Flutter 构建应用的工具
- 使用 Flutter 构建的热门应用
- 构建 Flutter 应用的成本
第二章 在Windows上搭建Flutter开发环境
- 使用镜像
- 系统要求
- 获取Flutter SDK
- 编辑器设置
- Android设置
- 起步: 配置编辑器
- 起步: 体验
- 体验热重载
第三章 编写您的第一个 Flutter App
- 创建 Flutter app
- 使用外部包(package)
- 添加一个 有状态的部件(Stateful widget)
- 创建一个无限滚动ListView
- 添加交互
- 导航到新页面
- 使用主题更改UI
第四章 Flutter开发环境搭建和调试
- 开发环境的搭建
- 模拟器的安装与调试
- 开发环境的搭建
- 模拟器的安装与调试
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数HarmonyOS鸿蒙开发工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年HarmonyOS鸿蒙开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上HarmonyOS鸿蒙开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新
如果你觉得这些内容对你有帮助,可以添加VX:vip204888 (备注鸿蒙获取)
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
66839157)]
[外链图片转存中…(img-6xCX2ZAP-1712866839158)]
[外链图片转存中…(img-Jng91fQD-1712866839158)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上HarmonyOS鸿蒙开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新
如果你觉得这些内容对你有帮助,可以添加VX:vip204888 (备注鸿蒙获取)
[外链图片转存中…(img-AcXxyEfH-1712866839158)]
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!