Riverpod - flutter 状态管理的应用
前言
Riverpod 是 Flutter 下知名度较高的状态管理依赖,同样出自 Provider 的开发者 rrousselGit 之手。
其实仔细去看 Riverpod 似乎只是 Provider 的拼写打乱了顺序,其提供了更简洁的API 设计,实现了依赖注入。
如果去看过 rrousselGit 的主页,你可以发现,他也是著名的 Flutter_hooks 的作者,RiverPod 也理所当然的拥有 hook 相关的血统 > HookConsumerWidget
, 我们可以在享受 hooks 的同时,直接使用Widget.ref(provider).watch
来监听变更并自动刷新页面。
为什么 Flutter 需要状态管理
Flutter 作为优秀的跨端框架,其使用的声明式UI有诸多优势,但嵌套的组件给数据传递带来了极大的挑战。
如果将数据在 组件类的构造函数中携带,并在数层中进行传递,随着代码量的提升,将会极大的增加代码的复杂和易理解程度。
因此状态管理组件出现了,其提供了一个清晰的模型来管理数据流,确保数据在正确的时机以正确的方式流动。这有助于避免数据不一致和难以追踪的 bug。通过集中的状态管理,我们可以更加容易的理解和增删需要传递的数据。
举个例子
我们可以使用最常见的 Flutter demo 来看, 在初始化完成项目之后,我们便可以看到这个例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| class MyHomePage extends StatefulWidget { const MyHomePage({super.key, required this.title}); final String title; @override State<MyHomePage> createState() => _MyHomePageState(); }
class _MyHomePageState extends State<MyHomePage> { int _counter = 0;
void _incrementCounter() { setState(() { _counter++; }); }
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ const Text( 'You have pushed the button this many times:', ), Text( '$_counter', style: Theme.of(context).textTheme.headlineMedium, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: const Icon(Icons.add), ), ); } }
|
因为 需要渲染的页面和按钮在 同一个组件 MyHomePage
下 因此我们可以很简单的在按钮点击的同时 setState 来使Flutter 感知数据的变化 并重新渲染页面。
组件分离
但是 多数情况下, 我们需要渲染的页面,和改变数据的按钮 并不在一个组件中,例如,如果我们将这个按钮单独封装在一个类中。这种情况下,我们应该如何在点击按钮的时候增加数据呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| class MyHomePage extends StatefulWidget { const MyHomePage({super.key, required this.title});
final String title;
@override State<MyHomePage> createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ const Text( 'You have pushed the button this many times:', ), Text( '${_counter._count}', style: Theme.of(context).textTheme.headlineMedium, ), ], ), ), floatingActionButton: const Button(), ); } }
class Button extends StatelessWidget { const Button({super.key});
@override Widget build(BuildContext context) { return FloatingActionButton( onPressed: _counter.increment, tooltip: 'Increment', child: const Icon(Icons.add), ); } }
|
使用 ChangeNotify
ChangeNotify 是 Flutter 下用于监听数据变更的组件,在这个场景下,我们可以使用其监听 counter 的变化, 并重新渲染页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| class Counter extends ChangeNotifier { int _count = 0; int get count => _count; void increment() { _count++; notifyListeners(); } }
Counter _counter = Counter(); ...... class _MyHomePageState extends State<MyHomePage> {
@override void dispose() { _counter.dispose(); super.dispose(); }
@override void initState() { super.initState(); _counter.addListener(() { setState(() {}); }); }
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ const Text( 'You have pushed the button this many times:', ), Text( '${_counter._count}', style: Theme.of(context).textTheme.headlineMedium, ), ], ), ), floatingActionButton: const Button(), ); } } ......
|
使用 RiverPod
再简单场景下,ChangeNotify
和 ValueNotify
已经能够胜任, 但对于复杂场景,还是有一定的局限性。
先添加如下依赖 (这里使用 Flutter_hooks 举例)
1 2 3 4 5 6 7 8
| dependencies: flutter_hooks: ^0.20.5 hooks_riverpod: ^2.5.1 riverpod_annotation: ^2.3.5
dev_dependencies: build_runner: ^2.4.11 riverpod_generator: ^2.4.0
|
别忘记在最外围增加一个 ProvideScope
1 2 3
| void main() { runApp(const ProviderScope(child: MyApp())); }
|
接着新增一个 counter_provider.dart
文件
1 2 3 4 5 6 7 8 9 10 11
| import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'counter_provider.g.dart';
@Riverpod(keepAlive: true) class Counter extends _$Counter { @override int build() => 0; void increment() { state++; } }
|
运行 代码生成
1
| $ dart run build_runner build
|
他将会生成一个 counter_provider.g.dart
文件
1 2 3 4
| ref.watch(counterProvider);
ref.read(counterProvider.notifier).increment;
|
完整的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| import 'package:daka/counter_provider.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
void main() { runApp(const ProviderScope(child: MyApp())); }
class MyApp extends StatelessWidget { const MyApp({super.key});
@override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), useMaterial3: true, ), home: const MyHomePage(), ); } }
class MyHomePage extends HookConsumerWidget { const MyHomePage({super.key});
@override Widget build(BuildContext context, WidgetRef ref) { return Scaffold( body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ const Text( 'You have pushed the button this many times:', ), Text( ref.watch(counterProvider).toString(), style: Theme.of(context).textTheme.headlineMedium, ), ], ), ), floatingActionButton: const Button(), ); } }
class Button extends HookConsumerWidget { const Button({super.key});
@override Widget build(BuildContext context, WidgetRef ref) {
final counter = ref.read(counterProvider.notifier);
return FloatingActionButton( onPressed: counter.increment, tooltip: 'Increment', child: const Icon(Icons.add), ); } }
|