【Flutter】状态管理插件的”四大天王“简单原理与使用方式对比
主流的状态管理插件的原理与使用对比
前言
目前的情况是,入门是从Getx开始的,后来有项目就转投了Bloc,目前如果有新项目的话想尝试一下 riverpod + hook 的方案。
目前除了 Flutter 主流的状态管理四大天王,Provider,Riverpod,GetX,Bloc之外,还有 MobX ,Redux ,Binders ,RxState ,GreenCat ,Seperated View ,StateX ,Rams,Signal 等等框架真的数不胜数。另外,Flutter官方也在积极研究统一的状态管理解决方案,未来可能会推出官方推荐的最佳实践。
学不完,真的学不完。
本文就只浅析一下四大天王的基本使用与基本原理与他们的差异。
一、Provider
Provider 它允许在Flutter应用中高效地传递数据和状态。使用Provider
时,通常不会修改原有数据类型来使其响应式。相反,Provider
通过在Widget树中向下传递数据和对象,使得依赖于这些数据的Widget可以重建来响应状态变化。
当数据变化时,你通常会调用notifyListeners()
(在ChangeNotifier
的实现中),这会通知所有监听器(即依赖该数据的Widgets),触发它们的重建。因此,Provider
不直接修改响应对象,而是依赖于ChangeNotifier
或其他机制来通知状态变化。
Provider
实际上是建立在Flutter的InheritedWidget
机制之上的。InheritedWidget
是一种有效的数据传递机制,允许数据在Widget树中从上向下传递。这意味着位于树中较低位置的Widgets可以访问在树中较高位置定义的数据。
Provider
简化了InheritedWidget
的使用,使状态管理和数据传递变得更加直观和简单。通过使用Provider
,开发者可以更容易地访问和操作跨组件的共享数据。
一句话总结 : Provider
是一种依赖注入框架,其状态管理功能基于InheritedWidget
和Listenable
实现。特别是ChangeNotifierProvider
是基于InheritedWidget
构建的,并且其内部通知机制依赖于ChangeNotifier
及其notifyListeners()
方法来实现。
代码示例:
class Counter with ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
}
class CounterWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final counter = Provider.of<Counter>(context); //需要buildContext的环境
return Text('Count: ${counter.count}');
}
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider( //这里通过ChangeNotifierProvider指定 ChangeNotifier
create: (_) => Counter(),
child: CounterWidget(),
);
}
}
二、Riverpod
Riverpod 是Provider的替代品,由同一作者开发。它解决了Provider的一些限制,如全局访问、改进的类型安全和测试性。与Provider不同,Riverpod不依赖于Widget树,因此可以更灵活地使用。
Riverpod被设计为高效且可预测的状态管理工具。由于它不依赖于BuildContext,可以避免Provider中的一些性能问题和限制。
Riverpod
同样不要求修改数据类型来使其响应式。它通过不同类型的Providers(如StateProvider
、StateNotifierProvider
等)来管理状态。
这些Providers管理着状态的读取和更新,当状态变化时,依赖于这些状态的Widgets会被自动重建。Riverpod
也提供了StateNotifier
,它类似于Provider
中的ChangeNotifier
,但是设计得更为灵活和强大。通过StateNotifier
,你可以管理更复杂的状态逻辑,同时保持UI的响应性。
代码示例 StateProvider:
final counter1Provider = StateProvider((ref) => 0); //创建与管理基本数据
class Home extends ConsumerWidget {
const Home({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
appBar: AppBar(title: const Text('Counter example')),
body: Center(
child: Consumer(
builder: (context, ref, _) {
final counter = ref.watch(counter1Provider);
return Text('$counter');
},
),
),
floatingActionButton: FloatingActionButton(
onPressed: (){
ref.read(counter1Provider.notifier).state++;
},
child: const Icon(Icons.add),
),
);
}
}
代码示例 ChangeNotifierProvider:
class Counter with ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
}
final counter2Provider = ChangeNotifierProvider((ref) => Counter()); //创建与管理对象
class Home extends ConsumerWidget {
const Home({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
appBar: AppBar(title: const Text('Counter example')),
body: Center(
child: Consumer(
builder: (context, ref, _) {
final counter = ref.watch(counter2Provider);
return Text('${counter.count}');
},
),
),
floatingActionButton: FloatingActionButton(
onPressed: (){
ref.read(counter2Provider.notifier).increment();
},
child: const Icon(Icons.add),
),
);
}
}
三、Provider VS Riverpod
Provider
和riverpod
确实在概念上有一些相似之处,因为它们都用于状态管理和依赖注入,但它们的实现和使用方式有所不同,这里解释这两种框架的不同之处以及InheritedWidget
在它们中的作用。
Provider
是一个流行的Flutter依赖注入库,主要是基于InheritedWidget
实现的。ChangeNotifierProvider
是它提供的一种Provider,用于将ChangeNotifier
的实例提供给其子Widget,并在状态变化时通知它们。当你使用ChangeNotifierProvider
时,你会在Widget树中的某个节点处提供一个ChangeNotifier
实例,并且只有当notifyListeners
被调用时,该Provider的依赖者(子Widget)才会重建。
ChangeNotifierProvider(
create: (context) => SomeModel(),
child: SomeWidget(),
)
这里ChangeNotifierProvider
使用了InheritedWidget
来传递数据,当使用context.watch()
或Consumer
等API时,它们实际上在内部查找最近的InheritedProvider<SomeModel>
来获取模型实例。
riverpod
则完全不同,它不依赖于InheritedWidget
。riverpod
是一个独立于Flutter框架之外的、可以与Flutter一起使用的状态管理库。在使用riverpod
时,你不会直接创建一个InheritedWidget
。相反,所有的providers都定义在全局范围内,你会使用ProviderScope
来确保providers可以在Widget树中任何地方使用。
在riverpod
中,ProviderScope
并不是一个InheritedWidget
,它更像是一个状态容器,允许你在应用的任何地方访问providers。ProviderScope
创建了一个状态存储,你可以在此之下的Widget树中任何地方通过context.read
、context.watch
或Consumer
等API访问providers。
所以尽管他们都是基于Listenable来通知的 ,但是他们的作用域和传递数据的方式是不同的。
Riverpod在使用时,需要将ProviderScope
放置在main()
函数中并作为runApp
的参数,确保了从MyApp
开始的整个Widget树都能够访问到通过riverpod
定义的providers。无论是状态管理还是依赖注入,这种做法都提供了一个全局的作用域,允许在应用的任何位置方便地使用这些providers。
如果不想改变 StatelessWidget、StatefulWidget 这样的布局结构,不想使用自定义的 ConsumerWidget 我们也能这么使用:
class Counter with ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
}
final counter2Provider = ChangeNotifierProvider((ref) => Counter()); //创建与管理对象
class Home extends StatelessWidget {
Home({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Counter example')),
body: Center(
child: Consumer(
builder: (context, ref, _) {
final counter = ref.watch(counter2Provider);
return Text('${counter.count}');
},
),
),
floatingActionButton: Consumer(
builder: (context, ref, _) {
return FloatingActionButton(
onPressed: () {
ref.read(counter2Provider.notifier).increment();
},
child: const Icon(Icons.add),
);
},
),
);
}
}
四、Getx
Getx 也是我们熟悉的选手,GetX是Flutter中一个很流行的库,提供了状态管理、路由管理、依赖注入等多种功能。以下是使用GetX进行状态管理的一些基础步骤
一般我们使用控制器来承载响应式变量:
import 'package:get/get.dart';
class UserController extends GetxController {
var userName = ''.obs;
var userAge = 0.obs;
void updateUser(String name, int age) {
userName.value = name;
userAge.value = age;
}
}
注册控制器:
final userController = Get.put(UserController());
在布局中使用状态:
Obx(() => Text("User Name: ${userController.userName}"));
当你需要更新状态时,直接在控制器中修改状态变量。
userController.updateUser('New Name', 25);
一旦调用了这个方法,所有使用该状态的 Obx() 内部部件会自动更新UI。
除了 obs 的方案,我们还能使用 GetBuilder 来管理指定范围的更新。只是这是手动的刷新,需要手动的调用update[tag] 来刷新指定的 GetBuilder 内部的布局。
其实 Getx 和 Provider 类似,需要 ChangeNotifier 或 GetxController 来承载,在布局中需要使用 Consumer 或者 Obx 来包裹布局。并且他们带有状态管理和依赖注入的功能。只是 Getx 改变了对象的属性,从 String 改为了 RxString。
并且 Getx 和 Provider/Riverpod 的实现原理也不同,GetX状态管理不是基于Flutter核心框架的InheritedWidget或Listenable来实现的。相反,它采用了自己的方式来实现状态管理和响应式编程,主要基于Rx(Reactive Extensions)实现的。
使用 .obs
标记的变量,实际上是一个Rx类型的对象,监听这些变量的Widget可以自动重新构建。
总之,GetX通过Rx和自己的依赖管理机制提供了一个强大且高效的状态管理解决方案。这与Flutter的InheritedWidget和Listenable等内建机制有本质的不同,使得开发者能够以更解耦和响应式的方式来管理应用状态。这种方式不仅提高了开发效率,还有助于提升应用的性能和可维护性。
五、Bloc
Bloc 是一种在Flutter中用于状态管理的架构模式。它的设计目的是将业务逻辑与界面展示分离,使得状态管理更加清晰和可预测。BLoC 使用了响应式编程(特别是通过使用 Streams),以实现数据流的管理和组件间的通信。
在 flutter_bloc
中,Bloc
和 Cubit
是用于状态管理的两个主要概念,它们都用于将业务逻辑从界面层分离出来,以帮助实现更可预测的状态管理和更易于测试的代码。
Bloc
相对于 Cubit
更复杂,它基于事件的概念。你需要定义事件和状态,然后实现事件到状态的映射,从 UI 层发出事件,Bloc
接受这些事件,并根据当前状态和接收到的事件通过 mapEventToState
函数来决定下一个状态。
Cubit
是 Bloc
的一个简化版本,它不依赖于事件的概念,而是直接对函数调用做出响应。在 Cubit
中,你直接调用一个函数来改变状态。这个函数内部会执行一些逻辑,并最终调用 emit
来发出新的状态。
与 Bloc
相比,Cubit
不需要定义事件,也不需要通过事件来触发状态变化,因此在实现上更为简洁。由于其简洁性,特别适用于那些不需要复杂事件处理的场景,可快速实施状态管理。Cubit
可以看做是 Bloc
的青春版,一个场景化的用法。
本次的示例就以 Cubit
状态为模板。后期单独讲 Bloc 的时候再两者单独说。
class CounterState extends Equatable {
final int counterValue;
final bool wasIncremented;
CounterState({required this.counterValue, required this.wasIncremented});
@override
List<Object> get props => [counterValue, wasIncremented];
}
class Counter2Cubit extends Cubit<CounterState> {
Counter2Cubit() : super(CounterState(counterValue: 0, wasIncremented: true));
void increment() => emit(CounterState(counterValue: state.counterValue + 1, wasIncremented: true));
void decrement() => emit(CounterState(counterValue: state.counterValue - 1, wasIncremented: false));
}
...
BlocProvider(
create: (_) => Counter2Cubit(),
child: BlocCounterPage(),
)
...
// Bloc对应的页面
class BlocCounterPage extends StatelessWidget {
const BlocCounterPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Counter')),
body: BlocBuilder<Counter2Cubit, CounterState>(
builder: (context, state) => Center(
child: Text(
'${state.counterValue}, was incremented: ${state.wasIncremented}',
),
),
),
floatingActionButton: Column(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () => context.read<Counter2Cubit>().increment(),
),
const SizedBox(height: 4),
FloatingActionButton(
child: const Icon(Icons.remove),
onPressed: () => context.read<Counter2Cubit>().decrement(),
),
],
),
);
}
}
常用 API :
提供者
BlocProvider
它提供了一种方式来将Bloc或Cubit传递给一个小部件树。任何子小部件都可以通过context来获取这个Bloc或Cubit。
BlocProvider(
create: (context) => CounterCubit(),
child: CounterPage(),
),
MultiBlocProvider
是BlocProvider的一个方便的封装,允许您一次性向子树提供多个Bloc或Cubit。
MultiBlocProvider(
providers: [
BlocProvider<CounterCubit>(
create: (BuildContext context) => CounterCubit(),
),
BlocProvider<ThemeCubit>(
create: (BuildContext context) => ThemeCubit(),
),
],
child: MyApp(),
),
RepositoryProvider
类似于 BlocProvider,但它不是用于提供 Bloc 或 Cubit,而是用于提供数据仓库或任何类型的数据服务。通过使用 RepositoryProvider,您可以在需要的地方注入数据源,以便它们可以在 widget 树中的任何地方被访问。
RepositoryProvider(
create: (context) => UserRepository(),
child: MyApp(),
),
MultiRepositoryProvider
允许您在一个地方注入多个数据仓库或服务。这在您的应用需要多个数据源时非常有用,可以帮助您保持代码的清晰和组织。
MultiRepositoryProvider(
providers: [
RepositoryProvider<UserRepository>(
create: (context) => UserRepository(),
),
RepositoryProvider<ProductRepository>(
create: (context) => ProductRepository(),
),
],
child: MyApp(),
),
在页面中接收Reposotory:
context.read<RepositoryA>(); //扩展的方式
RepositoryProvider.of<RepositoryA>(context) //非扩展的方式
接收者:
BlocBuilder
是一个Flutter小部件,它要求提供一个Bloc或Cubit和一个builder函数。当给定的Bloc或Cubit的状态发生变化时BlocBuilder将自动重建。注意它是返回Widget的。
BlocBuilder<CounterCubit, int>(
builder: (context, count) => Text('$count'),
),
如果想接收多个Provider的Bloc或Cubit状态变化,可以分开接收:
Column(
children: [
BlocBuilder<CounterCubit, CounterState>(
builder: (context, countState) {
// 使用 countState 构建 UI
},
),
BlocBuilder<ThemeCubit, ThemeState>(
builder: (context, themeState) {
// 使用 themeState 构建 UI
},
),
],
)
也可以嵌套接收:
BlocBuilder<CounterCubit, CounterState>(
builder: (context, countState) {
return BlocBuilder<ThemeCubit, ThemeState>(
builder: (context, themeState) {
// 结合 countState 和 themeState 构建 UI
},
);
},
)
BlocListener
是一个Flutter小部件,用于监听Bloc或Cubit的状态变化,并可以触发基于状态变化的动作。注意他不返回Widget。
BlocListener<CounterCubit, int>(
listener: (context, count) {
if (count == 10) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Count reached 10')),
);
}
},
child: Container(),
),
MultiBlocListener是BlocListener的一个方便的封装,允许您一次性向小部件树提供多个BlocListener。
MultiBlocListener(
listeners: [
BlocListener<CounterCubit, int>(
listener: (context, state) {},
),
BlocListener<ThemeCubit, ThemeData>(
listener: (context, themeData) {},
),
],
child: MyApp(),
),
BlocConsumer
是BlocBuilder和BlocListener的组合,它可以同时监听状态变化并重建UI。
BlocConsumer<CounterCubit, int>(
listener: (context, count) {
if (count == 10) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Count reached 10')),
);
}
},
builder: (context, count) => Center(child: Text('$count')),
),
BlocSelector
它允许您根据 Bloc 或 Cubit 的状态选择性地重建 UI 部分。与 BlocBuilder 类似,BlocSelector 用于侦听 Bloc 或 Cubit 状态的变化,但它提供了额外的选择功能,允许您指定一个选择器函数来决定何时重建 UI。
BlocSelector<UserBloc, UserState, String>(
selector: (state) => state.username,
builder: (context, username) {
return Text(username);
},
);
总的来说,熟悉 Android 开发的很容易上手 Bloc ,连命名的规范都很类似,开发模式也很类似。
bloc 的实现原理与 Provider和Riderpod 又不同,不是基于 InheritedWidget、Listenable 实现的,它利用了 Dart 的 Stream 和 StreamController 来处理不同状态的流和事件的分发。
六、原生实现
我们知道有些框架是基于原生的 nheritedWidget、Listenable 来实现状态管理,有些框架是基于 Stream 来实现状态管理,其实我们不使用框架直接手撕也是能自行实现的。
// 1. 定义状态
abstract class CounterState {}
class CounterInitial extends CounterState {}
class CounterValue extends CounterState {
final int value;
CounterValue(this.value);
}
// 2. 定义事件
abstract class CounterEvent {}
class CounterIncrement extends CounterEvent {}
// 3. 创建 BLoC 类
class CounterBloc {
int _counter = 0;
final _stateController = StreamController<CounterState>();
StreamSink<CounterState> get _inCounter => _stateController.sink;
Stream<CounterState> get counter => _stateController.stream;
final _eventController = StreamController<CounterEvent>();
Sink<CounterEvent> get counterEventSink => _eventController.sink;
CounterBloc() {
_eventController.stream.listen(_mapEventToState);
}
void _mapEventToState(CounterEvent event) {
if (event is CounterIncrement) {
_counter++;
_inCounter.add(CounterValue(_counter));
}
}
void dispose() {
_stateController.close();
_eventController.close();
}
}
// 4. 在 UI 中使用
void main() {
final counterBloc = CounterBloc();
runApp(MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('BLoC Example')),
body: StreamBuilder(
stream: counterBloc.counter,
initialData: CounterInitial(),
builder: (BuildContext context, AsyncSnapshot<CounterState> snapshot) {
if (snapshot.data is CounterValue) {
return Center(
child: Text(
'You hit me: ${(snapshot.data as CounterValue).value} times',
),
);
}
return Center(child: CircularProgressIndicator());
},
),
floatingActionButton: FloatingActionButton(
onPressed: () => counterBloc.counterEventSink.add(CounterIncrement()),
tooltip: 'Increment',
child: Icon(Icons.add),
),
),
));
}
其实同样的我们基于 ChangeNotifier + Listenable 一样能实现类似的功能:
class CounterModel with ChangeNotifier {
final ValueNotifier<int> _count = ValueNotifier(0);
ValueNotifier<int> get count => _count;
void increment() {
_count.value++;
notifyListeners();
}
}
使用:
class CounterPage extends StatefulWidget {
@override
_CounterPageState createState() => _CounterPageState();
}
class _CounterPageState extends State<CounterPage> {
final CounterModel _counterModel = CounterModel();
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('Listenable Example')),
body: ValueListenableBuilder(
valueListenable: _counterModel.count,
builder: (context, value, child) {
return Text('You have pushed the button $value times');
},
),
floatingActionButton: FloatingActionButton(
onPressed: () {
_counterModel.increment();
},
child: Icon(Icons.add),
),
),
);
}
}
只是没有依赖注入的效果或Provider的效果,对于对象的监听我们也可以做到:
class CounterModel with ChangeNotifier {
final ValueNotifier<UserProfile> _userProfile = ValueNotifier(UserProfile(0, "test name"));
final ValueNotifier<int> _count = ValueNotifier(0);
ValueNotifier<UserProfile> get userProfile => _userProfile;
ValueNotifier<int> get count => _count;
void increment() {
//如果是对象,需要每次都拷贝对象,更换对象内存地址
_userProfile.value = UserProfile(_userProfile.value.id + 1, _userProfile.value.name);
_count.value++;
notifyListeners();
}
}
使用的时候:
ValueListenableBuilder(
valueListenable: _counterModel.userProfile,
builder: (context, value, child) {
return Text('You have pushed the button ${value.id} times');
},
)
如果一个 ValueListenableBuilder 想要监听多个 valueListenable 对象,我们也能实现,只是我们需要自定义一个组合器
class CompositeValueListenable extends ChangeNotifier implements ValueListenable<List<dynamic>> {
final List<ValueListenable> _listenables;
late List<dynamic> _values;
CompositeValueListenable(this._listenables) {
// 初始化_values为监听的ValueListenable的当前值
_values = _listenables.map((listenable) => listenable.value).toList();
// 为每个ValueListenable添加监听器
for (var listenable in _listenables) {
listenable.addListener(_listener);
}
}
void _listener() {
// 当任何一个ValueListenable变化时,更新_values并通知监听者
_values = _listenables.map((listenable) => listenable.value).toList();
notifyListeners();
}
@override
List<dynamic> get value => _values;
@override
void dispose() {
// 移除所有监听器
for (var listenable in _listenables) {
listenable.removeListener(_listener);
}
super.dispose();
}
}
使用的时候:
class CounterPage extends StatefulWidget {
@override
_CounterPageState createState() => _CounterPageState();
}
class _CounterPageState extends State<CounterPage> {
final CounterModel _counterModel = CounterModel();
@override
Widget build(BuildContext context) {
final compositeListenable = CompositeValueListenable([
_counterModel.userProfile,
_counterModel.count,
]);
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('Listenable Example')),
body: ValueListenableBuilder(
valueListenable: compositeListenable,
builder: (context, values, child) {
return Text('当前的用户id为:${values[0].id} 他的完成数据为:${values[1]}');
},
),
floatingActionButton: FloatingActionButton(
onPressed: () {
_counterModel.increment();
},
child: Icon(Icons.add),
),
),
);
}
}
效果:
后记
当然除了以上这些状态管理的方案,还有后来者如 Signal ,也在崛起中。而 Signal 的实现思路又与上面四种方案有所差别。每个状态管理框架都试图以其独特的方式解决Flutter应用中状态管理的复杂性。搞得用户真的不知道怎么选择。
由于 Signal 的使用者还是少数不是主流,而以上主流的方案中根据难易程度,我觉得 GetX 是最简单入门的,其次是 Bloc 方案也很友好,再其次就是 RiverPod ,不过现在 RiverPod 都是结合 Hook 框架一起使用 hooks_riverpod: ^2.4.0
结合 flutter_hooks: ^0.20.1
最近也很流行。
我觉得这三种方案都是不错的选择,我之前的项目用到的是 Getx 现在的项目用的 Bloc 的方案,后期有新项目也会尝试 riverpod + hooks 的方案。
三种方案虽然内部实现的原理不同,但是使用下来感觉性能差异并不大,还是看开发者如何使用了,如果有这方面的性能对比,请各位大佬不吝赐教。
至于具体要选择哪一种方案真的还需要你综合使用门槛,社区活跃度等条件,稳定与熟悉也是比较重要的,最终还是需要你自己权衡,状态管理框架与页面和逻辑都关联比较深,是属于基建,一旦选择了后期很难更换。
如果你有其他的更多的更好的实现方式,也希望大家能评论区交流一起学习进步。如果我的文章有错别字,不通顺的,或者代码、注释、有错漏的地方,同学们都可以指出修正。
后期有时间我会单独的把几种框架单独拿出来详细说说,从一个 Android 开发者的角度如何快速入门这些状态管理插件。
由于不是具体的 Demo ,本文代码都已在文中贴出作为参考,如果感觉本文对你有一点的启发和帮助,还望你能点赞
支持一下,你的支持对我真的很重要。
Ok,这一期就此完结。
转载自:https://juejin.cn/post/7371720794979188777