Flutter响应式状态管理插件reactive_cubit的使用
Flutter响应式状态管理插件reactive_cubit的使用
1. 目标
开发一个Flutter包用于管理状态,而无需使用pub.dev上已有的任何状态管理包。该包应:
- 支持高效的状态管理并易于集成到各种规模的应用程序中。
- 通过仅在必要时更新UI组件来优化性能。
- 提供友好且直观的API以方便集成。
2. 设计需求与目标
设计需求:
- 清晰且可扩展的架构,支持从小型到复杂应用的各种场景。
- 支持简单的状态(单个变量)和复杂的状态(数组、映射或嵌套结构)。
功能需求:
- 提供以下方法:
- 初始化状态。
- 更新、更改和监听状态。
- 重置或删除状态。
- 确保UI组件仅在状态变化时重新渲染以优化性能。
- 支持同步和异步状态更新。
3. 关于RxCubit的简介
a. 概述
RxCubit
受 Cubit
模式的启发,并利用了 rxdart
包中的 BehaviorSubject
来有效地管理状态。与复杂的第三方状态管理库不同,RxCubit
使用流 (BehaviorSubject
) 来跟踪和广播状态的变化,从而优化UI更新。
b. 为什么RxCubit高效且优化
- 使用
RxDart
可以轻松处理异步操作。 BehaviorSubject
会保留最后广播的值并将其重新发送给新的订阅者,这在组件初始化时获取状态值时非常有用。- 使用
distinct()
方法确保只有当状态真正改变时才会更新,减少了不必要的UI重渲染。 select()
方法允许只监听状态的特定部分,避免过度监听并优化资源使用。
c. RxCubit的模式
UI组件直接调用Cubit的方法,而不是创建事件(如BLoC模式),这简化了实现并保证了应用程序的高性能。状态由Cubit发出,UI监听状态并相应地更新。
4. RxCubit的实现方式
a. 创建Cubit
RxCubit(State initialState)
: _initialState = initialState,
_controller = BehaviorSubject<State>.seeded(initialState) {
observer?.onCubitInit(this);
}
- 在创建
RxCubit
时,BehaviorSubject
会被初始化为初始状态。 - 可选的
observer
可用于在Cubit创建时进行调试或统计(例如打印日志)。
b. 访问当前状态流
Stream<State> get stream =>
_controller.stream.doOnListen(_onListen).doOnCancel(_onCancel);
State get state => _controller.value;
stream
: 返回状态的流,并通过doOnListen
和doOnCancel
来跟踪监听器的数量。state
: 返回当前状态。
c. 发布状态变更
void emit(State newState) {
if (!_controller.isClosed && state != newState) {
observer?.onStateChanged(this, state, newState);
_controller.add(newState);
}
}
emit()
方法仅在新状态不同于当前状态时才发布新状态。这可以防止不必要的状态发布和UI重渲染。
d. 重置为初始状态
void reset() {
if (!_controller.isClosed && state != _initialState) {
observer?.onStateChanged(this, state, _initialState);
_controller.add(_initialState);
}
}
reset()
方法将状态重置为初始状态。这在需要清除或刷新状态时非常有用。
e. 选择状态的特定属性
Stream<T> select<T>(T Function(State state) selector) {
return stream.map(selector).distinct().skip(1);
}
select()
方法允许监听状态的特定部分。它使用distinct()
方法来过滤不必要的更改。
f. 自动关闭流
void _onListen() {
_activeListeners++;
}
void _onCancel() {
_activeListeners--;
if (_activeListeners <= 0) {
close();
}
}
- 跟踪活动监听器的数量。当没有监听器时,Cubit会自动关闭以释放资源,防止内存泄漏。
5. 使用RxCubit的好处
- 高性能: 通过避免不必要的状态更新,减少UI重渲染。
- 易于使用和扩展: 明确的API使得容易集成到现有项目中,而不需要大量重构。
- 简单集成: 支持同步和异步状态更新,适用于复杂任务或API调用。
6. 结论
通过 RxCubit
,你可以成功构建一个高效且易于使用的状态管理解决方案,而不依赖外部包。使用 RxDart
,你可以通过流、选择器和合理过滤状态来有效管理状态。这种方案帮助Flutter应用保持高性能和良好的响应性。
StateObserver
StateObserver
用于监视 RxCubit
的生命周期和状态变化。它提供了处理状态变化、错误、初始化和取消Cubit的事件的方法。
主要方法:
-
onStateChanged<C extends RxCubit>(C cubit, dynamic previousState, dynamic newState)
- 目的: 当Cubit的状态发生变化时被调用。
- 参数:
cubit
: 状态发生变化的RxCubit
对象。previousState
: 变化前的状态。newState
: 变化后的状态。
-
onError<C extends RxCubit>(C cubit, Object error, StackTrace stackTrace)
- 目的: 当Cubit发生错误时被调用。
- 参数:
cubit
: 错误发生的RxCubit
对象。error
: 发生的错误。stackTrace
: 与错误相关的堆栈跟踪。
-
onCubitInit<C extends RxCubit>(C cubit)
- 目的: 当Cubit被初始化时被调用。
- 参数:
cubit
: 正在初始化的RxCubit
对象。
-
onCubitDispose<C extends RxCubit>(C cubit)
- 目的: 当Cubit被销毁时被调用。
- 参数:
cubit
: 正在销毁的RxCubit
对象。
RxMessageCubit
RxMessageCubit
继承自 RxCubit
,用于明确地分离逻辑,以便更清晰地更新UI和显示对话框。
主要方法:
-
void sendMessage(M message)
- 目的: 将消息发射到UI。
- 参数:
message
: 基于泛型类型的消息实例。
使用方法:
UI组件
late CounterMessageCubit cubit;
[@override](/user/override)
void initState() {
super.initState();
cubit = Provider.of<CounterMessageCubit>(context, listen: false);
// 监听消息流以接收通知
cubit.messageStream.listen(_onMessage);
}
void _onMessage(String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message)),
);
}
RxCubit
class CounterMessageCubit extends RxMessageCubit<int, String> {
CounterMessageCubit() : super(0);
void increment() {
emit(state + 1); // 在状态流上发射状态
sendMessage('Counter incremented to $state'); // 在消息流上发射消息
}
void decrement() {
emit(state - 1);
sendMessage('Counter decremented to $state');
}
}
使用示例
以下是使用 RxCubit
在Flutter应用中的一些简单示例。
void main() {
runApp(
RxCubitScope(
observer: MyObserver(),
child: const MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
[@override](/user/override)
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: HomeScreen(),
);
}
}
class HomeScreen extends StatefulWidget {
const HomeScreen({Key? key}) : super(key: key);
[@override](/user/override)
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
int _currentIndex = 0;
// 底部导航栏将导航到的屏幕列表
final List<Widget> _pages = [
const SingleCubitExample(),
const MultiCubitExample(),
];
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Navigation Example'),
),
body: IndexedStack(
index: _currentIndex,
children: _pages,
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
onTap: (index) {
setState(() {
_currentIndex = index;
});
},
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'SSE',
),
BottomNavigationBarItem(
icon: Icon(Icons.settings),
label: 'MTS',
),
],
),
);
}
}
文件 single_cubit_provider_example.dart
:
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:reactive_cubit/reactive_cubit.dart';
import 'detail_screen.dart';
class CounterState {
final bool isLoading;
final int count;
final bool isEven;
CounterState(
{required this.isLoading, required this.count, required this.isEven});
CounterState copyWith({int? count, bool? isEven, bool? isLoading}) {
return CounterState(
count: count ?? this.count,
isEven: isEven ?? this.isEven,
isLoading: isLoading ?? false,
);
}
[@override](/user/override)
bool operator ==(Object other) =>
identical(this, other) ||
other is CounterState &&
runtimeType == other.runtimeType &&
count == other.count &&
isEven == other.isEven &&
isLoading == other.isLoading;
[@override](/user/override)
int get hashCode => count.hashCode ^ isEven.hashCode;
}
class CounterCubit extends RxCubit<CounterState> {
CounterCubit()
: super(CounterState(count: 0, isEven: true, isLoading: false));
void increment() async {
emit(state.copyWith(isLoading: true));
await Future.delayed(Durations.extralong4);
final newCount = state.count + 1;
emit(
state.copyWith(count: newCount, isLoading: false),
);
}
void updateLabel() {
emit(
state.copyWith(isEven: state.count % 2 == 0),
);
}
}
class SingleCubitExample extends StatelessWidget {
const SingleCubitExample({super.key});
[@override](/user/override)
Widget build(BuildContext context) {
return Provider(
create: (_) => CounterCubit(),
child: const CounterWidget(),
);
}
}
class CounterWidget extends StatelessWidget {
const CounterWidget({super.key});
[@override](/user/override)
Widget build(BuildContext context) {
final cubit = Provider.of<CounterCubit>(context);
return Stack(
alignment: Alignment.center,
children: [
Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(width: double.infinity),
// 仅在 'count' 变化时重建
StreamBuilder<int>(
stream: cubit.select((state) => state.count),
builder: (context, snapshot) {
if (kDebugMode) {
print("Rebuild count");
}
return Text(
'Count: ${snapshot.data ?? 0}',
style: const TextStyle(fontSize: 24),
);
},
),
// 仅在 'isEven' 变化时重建
StreamBuilder<bool>(
stream: cubit.select((state) => state.isEven),
builder: (context, snapshot) {
if (kDebugMode) {
print("Rebuild title");
}
return Text(
snapshot.data == true ? 'Even' : 'Odd',
style: const TextStyle(fontSize: 24),
);
},
),
ElevatedButton(
onPressed: cubit.increment,
child: const Text('Increment'),
),
ElevatedButton(
onPressed: cubit.updateLabel,
child: const Text('Update label'),
),
ElevatedButton(
onPressed: cubit.reset,
child: const Text('Reset state'),
),
ElevatedButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) {
return Provider(
create: (BuildContext context) => CounterMessageCubit(),
child: const DetailScreen(),
);
},
),
);
},
child: const Text('Go to detail'),
),
],
),
Container(
color: Colors.transparent,
child: StreamBuilder<bool>(
stream: cubit.select((state) => state.isLoading),
builder: (context, snapshot) {
if (kDebugMode) {
print("Rebuild Loading");
}
return Offstage(
offstage: !(snapshot.data ?? false),
child: const IgnorePointer(
ignoring: true,
child: SizedBox.expand(
child: Center(
child: CircularProgressIndicator(),
),
),
),
);
},
),
),
],
);
}
}
更多关于Flutter响应式状态管理插件reactive_cubit的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter响应式状态管理插件reactive_cubit的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
reactive_cubit
是一个用于 Flutter 的响应式状态管理插件,它基于 cubit
和 flutter_bloc
,但提供了更简洁的 API 来处理响应式状态。reactive_cubit
允许你轻松地管理应用程序的状态,并且可以自动处理状态的更新和通知。
以下是 reactive_cubit
的基本使用方法:
1. 添加依赖
首先,你需要在 pubspec.yaml
文件中添加 reactive_cubit
依赖:
dependencies:
flutter:
sdk: flutter
reactive_cubit: ^0.1.0
然后运行 flutter pub get
来安装依赖。
2. 创建 ReactiveCubit
ReactiveCubit
是一个继承自 Cubit
的类,它允许你管理状态。你可以通过继承 ReactiveCubit
来创建自己的状态管理类。
import 'package:reactive_cubit/reactive_cubit.dart';
class CounterCubit extends ReactiveCubit<int> {
CounterCubit() : super(0);
void increment() => emit(state + 1);
void decrement() => emit(state - 1);
}
在这个例子中,CounterCubit
管理一个 int
类型的状态,并提供了 increment
和 decrement
方法来更新状态。
3. 使用 ReactiveCubit
在 UI 中
你可以使用 ReactiveCubitBuilder
或 ReactiveCubitListener
来在 UI 中响应状态的变化。
import 'package:flutter/material.dart';
import 'package:reactive_cubit/reactive_cubit.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
[@override](/user/override)
Widget build(BuildContext context) {
return MaterialApp(
home: ReactiveCubitProvider(
create: (context) => CounterCubit(),
child: CounterPage(),
),
);
}
}
class CounterPage extends StatelessWidget {
[@override](/user/override)
Widget build(BuildContext context) {
final counterCubit = ReactiveCubitProvider.of<CounterCubit>(context);
return Scaffold(
appBar: AppBar(title: Text('Counter Example')),
body: Center(
child: ReactiveCubitBuilder<CounterCubit, int>(
builder: (context, state) {
return Text('Count: $state');
},
),
),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
onPressed: () => counterCubit.increment(),
child: Icon(Icons.add),
),
SizedBox(height: 10),
FloatingActionButton(
onPressed: () => counterCubit.decrement(),
child: Icon(Icons.remove),
),
],
),
);
}
}
在这个例子中,ReactiveCubitProvider
用于在整个应用中提供 CounterCubit
实例。ReactiveCubitBuilder
用于在 UI 中响应状态的变化,并更新显示的内容。
4. 处理副作用
如果你需要在状态变化时执行一些副作用操作(例如导航、显示对话框等),可以使用 ReactiveCubitListener
。
class CounterPage extends StatelessWidget {
[@override](/user/override)
Widget build(BuildContext context) {
final counterCubit = ReactiveCubitProvider.of<CounterCubit>(context);
return Scaffold(
appBar: AppBar(title: Text('Counter Example')),
body: Center(
child: ReactiveCubitListener<CounterCubit, int>(
listener: (context, state) {
if (state == 10) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Count reached 10!')),
);
}
},
child: ReactiveCubitBuilder<CounterCubit, int>(
builder: (context, state) {
return Text('Count: $state');
},
),
),
),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
onPressed: () => counterCubit.increment(),
child: Icon(Icons.add),
),
SizedBox(height: 10),
FloatingActionButton(
onPressed: () => counterCubit.decrement(),
child: Icon(Icons.remove),
),
],
),
);
}
}
在这个例子中,当计数达到 10 时,ReactiveCubitListener
会显示一个 SnackBar。
5. 使用 ReactiveCubitSelector
ReactiveCubitSelector
允许你选择部分状态并在其变化时重建 UI。
class CounterPage extends StatelessWidget {
[@override](/user/override)
Widget build(BuildContext context) {
final counterCubit = ReactiveCubitProvider.of<CounterCubit>(context);
return Scaffold(
appBar: AppBar(title: Text('Counter Example')),
body: Center(
child: ReactiveCubitSelector<CounterCubit, int, bool>(
selector: (state) => state >= 10,
builder: (context, isGreaterThanOrEqualTo10) {
return Text('Is count >= 10? $isGreaterThanOrEqualTo10');
},
),
),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
onPressed: () => counterCubit.increment(),
child: Icon(Icons.add),
),
SizedBox(height: 10),
FloatingActionButton(
onPressed: () => counterCubit.decrement(),
child: Icon(Icons.remove),
),
],
),
);
}
}