Flutter状态管理之Riverpod
Posted 唯鹿
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flutter状态管理之Riverpod相关的知识,希望对你有一定的参考价值。
最近一两个月在一些Flutter的话题中不断的见到了Riverpod
这个关键词,细看后发现它是Flutter状态管理的一个新方式。
Flutter的状态管理方式有很多,Redux
、 Bloc
、 MobX
、Provider
等等。单单一个Provider,我也见到了各种组合,例如ChangeNotifier + Provider / StateNotifier + Provider( + freezed)。各种方式各有千秋,我们根据自己的习惯和项目的情况去选择就好,这里不做讨论。本篇只是来介绍一下Riverpod
,给大家提供一个新的选择。
1.介绍
Riverpod和Provider
师出同门,都来自作者Remi,Riverpod
可以被认为是Provider
的重写,来实现原本不可能的功能。就像它的名字一样,字母与Provider
相同,但是又不相同。
你可以理解Riverpod
是Provider
的升级版,解决了Provider
的一些痛点:
-
Provider
是InheritedWidget
的封装,所以在读取状态时需要BuildContext
。这导致了许多的限制,许多新手在不理解InheritedWidget
和BuildContext
时,跨页面获取状态经常会ProviderNotFoundException
。而Riverpod
不再依赖Flutter,也就是没有使用InheritedWidget
,所以也不需要BuildContext
。 -
读取对象是编译安全的。没有那么多的运行时异常。
-
能够有多个相同类型的provider。
-
provider可以是私有的。
-
当不再使用provider的状态时,将其自动回收。
当然目前Riverpod
也有一些不足(0.14.0+3版本):
- 毕竟诞生不久,它还不能保证是完全稳定的。
- 可能后期会有API的破坏性改动。(比如在0.7.0就有不少Breaking,导致我之前写的部分示例内容就报错了。)
- 目前生产环境中使用需要谨慎。
2.如何选择
作者提供了Riverpod
的三种方式,怎样选择如下图:
本篇不引入flutter_hooks相关内容,这里我就选择flutter_riverpod
。那么将它添加到pubspec.yaml
中:
flutter_riverpod: ^0.14.0+3
然后执行flutter pub get
。
3.基础使用
Provider
这里使用Riverpod
的Provider
需要三步就可以。
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
// 1.创建一个全局的provider,里面储存“Hello World!”
final Provider<String> helloWorldProvider = Provider((_) => 'Hello World!');
void main()
runApp(
// 2.添加“ProviderScope”。所有使用Riverpod的Flutter程序都必须
// 在widget tree的根部添加它,用来储存各个provider。
ProviderScope(
child: MyApp(),
),
);
class MyApp extends StatelessWidget
@override
Widget build(BuildContext context)
return MaterialApp(
title: 'Riverpod Example',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: ProviderExample(),
);
// 3.使用“ConsumerWidget”,在“build”中获取对应的provider
class ProviderExample extends ConsumerWidget
@override
Widget build(BuildContext context, ScopedReader watch)
final String value = watch(helloWorldProvider);
return Scaffold(
appBar: AppBar(title: Text('Provider Example')),
body: Center(
child: Text(value),
),
);
这里储存“Hello World!” 使用的是Provider
,它提供一个永远不变的对象。不过大部分场景下状态都是可变的,下面用计数器来举例。
StateProvider
在“Hello World”的基础上,做两点修改即可。
-
定义一个全局常量
StateProvider
。final StateProvider<int> counterProvider = StateProvider((_) => 0);
-
import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; class StateProviderExample extends StatelessWidget @override Widget build(BuildContext context) return Scaffold( appBar: AppBar( title: Text('StateProvider Example'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text( 'You have pushed the button this many times:', ), Consumer( builder: (context, watch, _) /// 使用Consumer(ConsumerWidget的封装),控制刷新的范围。 int count = watch(counterProvider).state; return Text( '$count', style: Theme.of(context).textTheme.headline4, ); , ), ], ), ), floatingActionButton: FloatingActionButton( /// 使用read获取counterProvider,操作state。 onPressed: () => context.read(counterProvider).state++, tooltip: 'Increment', child: Icon(Icons.add), ), );
如果你的状态比较复杂可以使用ChangeNotifierProvider
,如果习惯使用StateNotifier
,可以使用StateNotifierProvider
。其实StateProvider
的内部是StateController
,也还是StateNotifier
。源码如下;
class StateProvider<T>
extends AlwaysAliveProviderBase<StateController<T>, StateController<T>>
StateProvider(
Create<T, ProviderReference> create,
String name,
) : super((ref) => StateController(create(ref)), name);
...
class StateController<T> extends StateNotifier<T>
StateController(T state) : super(state);
@override
T get state => super.state;
@override
set state(T value) => super.state = value;
StateNotifierProvider
的用法与StateProvider
基本一致,这里就不贴出来了,有兴趣的可以点击这里查看。
2021-04-19更新:
0.14.0对StateNotifierProvider
的语法有破坏性变化,避免了StateNotifierProvider
的错误使用。具体见文档。
ChangeNotifierProvider
这部分没啥说的,注意ChangeNotifier
与StateNotifier
的区别,需要自己调用notifyListeners
通知变更。
final ChangeNotifierProvider<Counter> _counterProvider = ChangeNotifierProvider((_) => Counter());
class Counter extends ChangeNotifier
int _count = 0;
int get count => _count;
void increment()
_count++;
notifyListeners();
void decrement()
_count--;
notifyListeners();
class ChangeProviderNotifierExample extends StatelessWidget
@override
Widget build(BuildContext context)
return Scaffold(
appBar: AppBar(
title: Text('ChangeNotifierProvider Example'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Consumer(
builder: (context, watch, _)
int count = watch(_counterProvider).count;
return Text(
'$count',
style: Theme.of(context).textTheme.headline4,
);
,
),
],
),
),
floatingActionButton: FloatingActionButton(
/// 使用read获取counterProvider。
onPressed: () => context.read(_counterProvider).increment(),
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
FutureProvider
final FutureProvider<String> futureProvider = FutureProvider((_) async
/// 延时3s
await Future.delayed(const Duration(seconds: 3));
return 'Riverpod';
);
class FutureProviderExample extends StatelessWidget
@override
Widget build(BuildContext context)
return Scaffold(
appBar: AppBar(
title: Text('FutureProvider Example'),
),
body: Center(
child: Consumer(
builder: (context, watch, _)
AsyncValue<String> futureProviderValue = watch(futureProvider);
/// 根据相应状态展示
return futureProviderValue.when(
loading: () => CircularProgressIndicator(),
error: (error, stack) => Text('Oops, something unexpected happened'),
data: (value) => Text(
'Hello $value',
style: Theme.of(context).textTheme.headline4,
),
);
,
),
),
);
作者也提供了StreamProvider
。用法大同小异,有兴趣的可以查看我的示例代码。
ProviderListener
如果你希望在Widget Tree上监听provider的状态变化,可以使用ProviderListener
。用上面的计数器例子,当计数器为5时,触发监听。
ProviderListener<StateController<int>>(
provider: counterProvider,
onChange: (_, counter)
if (counter.state == 5)
print('当前计数器为5,触发监听。');
,
child: Consumer(
builder: (context, watch, _)
int count = watch(counterProvider).state;
return Text(
'$count',
style: Theme.of(context).textTheme.headline4,
);
,
),
),
ScopeProvider
一般我们在实现一个列表的Item时,需要传入相应的index大致如下:
ListView.builder(
itemCount: 50,
itemBuilder: (context, index)
return ProductItem(index: index);
,
)
如果使用ScopedProvider
并结合 ProviderScope
,就可以简单的获取index,不必从构造方法接收它。使用起来很简单,直接上代码:
/// 定义ScopedProvider
final ScopedProvider<int> currentProductIndex = ScopedProvider<int>(null);
class ScopeProviderExample extends StatelessWidget
@override
Widget build(BuildContext context)
return Scaffold(
appBar: AppBar(
title: Text('ScopedProvider'),
),
body: ListView.builder(
itemCount: 50,
itemBuilder: (context, index)
return ProviderScope(
overrides: [
/// 修改value
currentProductIndex.overrideWithValue(index),
],
/// 使用'const'关键字实例化了“ProductItem”,
/// 但仍然可以在内部动态获取内容。
child: const ProductItem(),
);
,
),
);
class ProductItem extends ConsumerWidget
const ProductItem(Key key): super(key: key);
@override
Widget build(BuildContext context, ScopedReader watch)
/// 获取相应index
final index = watch(currentProductIndex);
return ListTile(title: Text('item $index'));
4.修饰符
family
family
的作用是可以在获取provider时可以添加一个参数。直接上例子,一看便知:
/// 使用family,可以在获取provider时传入city
final _weatherProvider = Provider.family<String, String>((ref, city)
return '$city (Sunny)';
);
class FamilyExample extends StatelessWidget
@override
Widget build(BuildContext context)
return Scaffold(
appBar: AppBar(title: Text('Family')),
body: Center(
child: Consumer(
builder: (context, watch, _)
/// 这里可以传参“London”
final String weather = watch(_weatherProvider('London'));
return Text('$weather',);
,
),
),
);
注意: 使用family
时传入的参数是有限制的。比如bool
、 int
、 double
、 String
、常量或是重写了==
和hashCode
的不可变对象。
autoDispose
前面我们的例子中,创建的provider因为保存在Widget Tree的根部。所以即使页面关闭,再次进入页面时会获取之前的状态。
这显然是不灵活的,那么这里就可以使用autoDispose
,它可以在我们不再使用provider时,自动将其销毁。那么合理的使用它可以避免内存泄漏。
比如之前的计数器例子,只需加一个autoDispose
就可以避免此类问题。
final stateProvider = StateProvider.autoDispose((_) => 0);
如果你需要自定义dispose事件,可以使用onDispose
。比如你的provider中有网络请求(使用Dio):
final myProvider = FutureProvider.autoDispose((ref) async
final cancelToken = CancelToken();
// 当provider被销毁时,取消http请求
ref.onDispose(() => cancelToken.cancel());
// http请求
final response = await dio.get('path', cancelToken: cancelToken);
// 如果请求成功完成,则保持该状态。
ref.maintainState = true;
return response;
);
上面代码中出现了ref.maintainState
,这个参数默认为false。如果用户离开页面并且请求失败,下次则将再次执行该请求。但是,如果请求成功完成(maintainState为true),则将保留状态,下次重新进入页面时不会触发新的请求。
使用autoDispose
可以达到限制provider是全局还是局部作用。这样一来,可以更方便的解决跨页面使用provider的问题。
5.进阶使用
Combining providers
1.如果创建的provider需要另一个provider的状态,这时就需要使用ProviderReference
的read
方法。
下面的示例是,给予城市和国家的provider,当创建locationProvider时,获取城市和国家的状态。
final Provider<String> cityProvider = Provider((ref) => 'London');
final Provider<String> countryProvider = Provider((ref) => 'England');
final Provider<Location> locationProvider = Provider((ref) => Location(ref));
class Location
Location(this._ref);
final ProviderReference _ref;
String get label
/// read 获取
final city = _ref.read(cityProvider);
final country = _ref.read(countryProvider);
return '$city ($country)';
使用Riverpod
就可以提供多个相同类型的Provider,这也是相比Provider
的一个优点。
2.如果获取的状态值会发生变化,我们需要监听它。可以使用ProviderReference
的watch
方法。
下面的示例是,给予城市provider,当城市变化时,天气也相应变化。
final StateProvider<String> cityProvider = StateProvider((ref) => 'London');
final StateProvider<String> weatherProvider = StateProvider((ref)
/// watch监听
final String city = ref.watch(cityProvider).state;
return '$city 以上是关于Flutter状态管理之Riverpod的主要内容,如果未能解决你的问题,请参考以下文章