状态管理处理应用程序数据流动和 UI 更新的关键概念。在 Flutter 应用程序中,状态管理确保应用程序 UI 和数据保持同步,共享和同步数据,并提供良好的代码结构和可维护性。
Flutter 提供了 StatefulWidget
作为最基本的状态管理方法。有状态组件可以存储和更新自身状态,适用于简单的场景和局部状态。
然而,StatefulWidget
存在以下问题:
StatelessWidget
,StatefulWidget
在状态变化时会导致更多组件重建,可能影响应用程序性能,尽管Flutter已经进行了性能优化。StatefulWidget
具有复杂的生命周期,需要处理多个生命周期方法(如initState
、didUpdateWidget
和dispose),导致代码复杂和难以管理。StatefulWidget
具有内部状态,编写单元测试和集成测试变得更加困难,可能影响应用程序的质量和可靠性。StatefulWidget
的状态通常与特定实例紧密耦合,降低了组件的可重用性。在 Flutter 中,还有其他的状态管理方法可供选择,以下是一些常见的状态管理方法。
对于较小的应用程序或有限的状态共享需求较为合适
。各种规模的应用程序
,具有良好的可扩展性和灵活性。需要更高度可控和可测试性的应用程序
。需要处理复杂业务逻辑和大量数据流的应用程序
。需要严格的状态管理和可预测性的应用程序
。具体选择什么样的状态管理方法,这取决于你应用程序的需求、复杂性和个人喜好。不同的方法有不同的优缺点,因此在选择状态管理方法时,请务必充分了解每种方法的特点,并权衡其适用性。
究其原因,还是 Riverpod 的一些主要特点比较给力,与我们的需求契合,且听我慢慢道来……
总之,Riverpod 是一个强大的状态管理库,适用于各种规模的 Flutter 应用程序。它提供了不可变性、类型安全性、无需 BuildContext 的访问、可组合性、易于测试和家族功能等多种优点。如果你正在寻找一个现代、灵活且易于使用的状态管理解决方案,Riverpod 是一个值得考虑的选择。
https://docs-v2.riverpod.dev/zh-hans/
flutter pub add flutter_riverpod dev:custom_lint dev:riverpod_lint riverpod_annotation dev:build_runner dev:riverpod_generator
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'main.g.dart';
// 我们创建一个 “provider”,它将用于保存一个值(这里是 “Hello world”)。
// 通过使用一个 provider,我们能够模拟或覆盖被暴露的值。
String helloWorld(HelloWorldRef ref) {
return 'Hello world';
}
void main() {
runApp(
// 为了能让组件读取 provider,我们需要将整个
// 应用都包裹在 “ProviderScope” 组件内。
// 这里也就是存储我们所有 provider 状态的地方。
ProviderScope(
child: MyApp(),
),
);
}
// 扩展来自 Riverpod 的 HookConsumerWidget 而不是 HookWidget
class MyApp extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final String value = ref.watch(helloWorldProvider);
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Example')),
body: Center(
// 读取 provider 的值
// 此处为了方便查看,设置了大字体
child: Text(value, style: const TextStyle(fontSize: 40),),
),
),
);
}
}
# --delete-conflicting-outputs 可选,会在生成代码冲突的时候,删除原来的代码,重新生成
flutter pub run build_runner build --delete-conflicting-outputs
main.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:study/pages/HomePage.dart';
void main() {
runApp(
// 为了能让组件读取 provider,我们需要将整个
// 应用都包裹在 “ProviderScope” 组件内。
// 这里也就是存储我们所有 provider 状态的地方。
const ProviderScope(
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return const MaterialApp(
home: HomePage(),
);
}
}
home_page.dart
/lib/pages/home_page.dart
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import '../states/hello_state.dart';
class HomePage extends HookConsumerWidget {
const HomePage({super.key});
Widget build(BuildContext context, WidgetRef ref) {
final hello = ref.watch(helloStateProvider);
return Scaffold(
appBar: AppBar(
title: const Text('Home'),
),
body: Center(
child: SizedBox(
height: 400,
child: Column(
children: [
// 文本
Text(hello.hello, style: const TextStyle(fontSize: 40),),
// 更新文本
ElevatedButton(
style: ButtonStyle(minimumSize: MaterialStateProperty.all(const Size(200, 50))),
onPressed: () {
ref.read(helloStateProvider.notifier).setHello("文本更新了!");
},
child: const Text('Update'),
),
],
),
)),
);
}
}
hello_state.dart
lib/state/hello_state.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
class HelloState {
final String hello;
HelloState({
this.hello = 'Hello World',
});
}
class HelloStateProvider extends StateNotifier<HelloState> {
HelloStateProvider() : super(HelloState());
void setHello(String hello) {
state = HelloState(
hello: hello,
);
}
}
final helloStateProvider = StateNotifierProvider<HelloStateProvider, HelloState>(
(ref) => HelloStateProvider(),
);
代码生成是指使用工具为我们生成代码。
在Dart中,它的缺点是需要额外的步骤来“编译”应用。 尽管这个问题可能会在不久的将来得到解决, 但Dart团队正在研究并解决这个问题的潜在方案。
使用Riverpod时,代码生成是完全可选的。 当然你也完全可以不使用。
与此同时,Riverpod支持代码生成,且推荐你使用它。
hello_state.dart
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'hello_state.g.dart';
class HelloList extends _$HelloList {
List<String> build() {
return ["hello world!"];
}
void addHello(String hello) {
state = [...state, hello];
}
}
# --delete-conflicting-outputs 可选,会在生成代码冲突的时候,删除原来的代码,重新生成
flutter pub run build_runner build --delete-conflicting-outputs
home_page.dart
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import '../states/hello_state.dart';
class HomePage extends HookConsumerWidget {
const HomePage({super.key});
Widget build(BuildContext context, WidgetRef ref) {
final List<String> hellos = ref.watch(helloListProvider);
return Scaffold(
appBar: AppBar(
title: const Text('Home'),
),
body: Center(
child: SizedBox(
height: 400,
child: Column(
children: [
// 文本:遍历 hellos 列表
...hellos.map((e) => Text(e, style: const TextStyle(fontSize: 40),)),
// 更新文本
ElevatedButton(
style: ButtonStyle(minimumSize: MaterialStateProperty.all(const Size(200, 50))),
onPressed: () {
// 获取当前时间
DateTime now = DateTime.now();
ref.read(helloListProvider.notifier).addHello("hello ${now.second}");
},
child: const Text('Update'),
),
],
),
)),
);
}
}
你可能在想:“如果在Riverpod中代码生成是可选的,为什么要使用?”
让你的代码生活更简单。
这包括但不限于:
FutureProvider
、Provider
还是其他 provider。仅需写下你的逻辑, Riverpod将为你选择最合适的provider。与此同时,许多应用程序中已经使用了代码生成比如 Freezed 或 json_serializable。 在这种情况下,你的项目可能已经为代码生成配置好了,使用Riverpod应该很简单。
part 'film_state.g.dart';
part 'film_state.freezed.dart';
class FilmState with _$FilmState {
factory FilmState({
// EasyRefresh 控制器
EasyRefreshController? controller,
// 当前页码
int? pageNum,
// 每页数量
int? pageSize,
// 电影列表
List<FilmModel>? list,
// 没有更多数据了
bool? noMore,
}) = _FilmState;
}
class FilmStateNotifier extends _$FilmStateNotifier {
FutureOr<FilmState> build() async {
return FilmState(
controller: EasyRefreshController(),
pageNum: 0,
pageSize: 10,
list: [],
noMore: false,
);
}
// 加载电影列表
FutureOr<void> loadFilmList() async {
// 分页查询参数
final dto = FilmPageRequestModel();
dto.pageSize = state.value!.pageSize;
dto.pageNum = state.value!.pageNum;
// 加载电影列表
BasePageModel basePageModel = await FilmApi.page(dto);
basePageModel.content = basePageModel.decode(FilmModel(), basePageModel.content!);
if (basePageModel.content!.isNotEmpty) {
state.value!.list!.addAll(basePageModel.content!.cast<FilmModel>());
state.value!.noMore = false;
} else {
state.value!.noMore = true;
}
}
// 下拉刷新
FutureOr<void> refresh() async {
state.value!.list!.clear();
state.value!.pageNum = 0;
await loadFilmList();
state.value!.controller!.finishRefresh(IndicatorResult.success, true);
}
// 上拉加载
FutureOr<dynamic> loadMore() async {
state.value!.pageNum = state.value!.pageNum! + 1;
await loadFilmList();
if (!state.value!.noMore!) {
state.value!.controller!.finishLoad(IndicatorResult.success, true);
} else {
state.value!.controller!.finishLoad(IndicatorResult.noMore, true);
}
}
}
class FilmPage extends ConsumerWidget {
const FilmPage({super.key});
Widget build(BuildContext context, WidgetRef ref) {
final stateProvider = ref.watch(filmStateNotifierProvider);
final stateNotifier = ref.watch(filmStateNotifierProvider.notifier);
return stateProvider.when(
data: (state) {
return SimpleEasyRefresher(
easyRefreshController: state.controller,
onLoad: stateNotifier.loadMore,
onRefresh: stateNotifier.refresh,
childBuilder: (context, physics) {
return ListView.builder(
physics: physics,
itemCount: state.list!.length,
itemBuilder: (BuildContext context, int index) {
return GestureDetector(
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (context) => FilmDetailsPage(filmModel: state.list![index]))),
child: Container(color: GlobalColor.bgc, child: $BuildFilmItem(state.list![index])),
);
},
);
},
);
},
loading: () => const CircularProgressIndicator(),
error: (error, stackTrace) => Text('Error: $error'),
);
}
}
ref.read
与 ref.watch
ref.watch
ref.watch
方法用于订阅状态,并在状态发生变化时重新构建小部件。它返回一个可监听的状态。ref.watch
订阅状态时,如果状态发生变化,相关的小部件会被重新构建,以更新界面展示。ref.watch
方法在小部件的build
方法中使用,确保当状态变化时,与状态相关的部分会被更新。ref.read
:ref.read
方法用于读取状态,但不会订阅状态变化。ref.read
读取状态时,它会立即返回当前的状态值,但不会自动更新界面。ref.read
方法在小部件的build
方法之外使用,例如在回调函数、事件处理程序或其他地方需要读取状态的情况下使用。ref.watch
会订阅状态的变化并自动更新界面,适用于需要及时响应状态变化的情况。ref.read
会立即返回当前的状态值,适用于不需要及时更新界面的情况。XXXNotifierProvider
和XXXNotifierProvider.notifier
的区别XXXNotifierProvider
XXXNotifierProvider
是一个 Provider
对象,它负责提供状态值。ref.watch(XXXNotifierProvider)
时,它会订阅状态的变化并返回状态值。XXXNotifierProvider.notifier
notifier
是XXXNotifierProvider
提供的一个特殊属性,它指向状态通知器(notifier)对象。ref.watch(XXXNotifierProvider.notifier)
时,它会订阅状态通知器的变化并返回状态通知器对象。XXXNotifierProvider
返回的是Provider对象,用于访问状态值。XXXNotifierProvider.notifier
返回的是状态通知器对象,用于管理状态的变更和执行业务逻辑。