Flutter轻量级状态管理插件bloc_small的使用
Flutter轻量级状态管理插件bloc_small的使用
简介
bloc_small
是一个基于 flutter_bloc
的轻量级且简化的业务逻辑组件(BLoC)库。它简化了状态管理,使其更加直观,同时保持了 BLoC 模式的优点。
功能
- 使用
flutter_bloc
简化 BLoC 模式实现 - 基于 Dart Streams 的易用反应式编程
- 自动资源管理和清理
- 与
GetIt
集成用于依赖注入 - 支持加载状态和错误处理
- 流畅的状态更新和事件处理
- 内置支持异步操作
- 与
freezed
集成以支持不可变状态和事件类 - 增强的
ReactiveSubject
具有强大的流转换方法
安装
在 pubspec.yaml
文件中添加 bloc_small
依赖:
dependencies:
bloc_small:
injectable:
freezed:
dev_dependencies:
injectable_generator:
build_runner:
freezed_annotation:
然后运行:
flutter pub run build_runner build --delete-conflicting-outputs
注意:每次修改使用 Freezed 或 Injectable 注解的文件时,都需要运行生成命令,以便为您的 BLoCs、事件和状态生成必要的代码。
核心概念
类名 | 描述 | 基础类 | 目的 |
---|---|---|---|
MainBloc |
BLoC 模式的基础实现 | MainBlocDelegate |
处理事件并发出状态 |
MainCubit |
简化状态管理的替代方案 | MainCubitDelegate |
直接状态突变而不使用事件 |
MainBlocEvent |
所有事件的基类 | - | 触发 BLoC 中的状态变化 |
MainBlocState |
所有状态的基类 | - | 表示应用程序状态 |
CommonBloc |
全局功能管理器 | - | 管理加载状态和通用功能 |
BLoC 模式示例:
[@injectable](/user/injectable)
class CounterBloc extends MainBloc<CounterEvent, CounterState> {
CounterBloc() : super(const CounterState.initial()) {
on<Increment>(_onIncrement);
}
}
Cubit 模式示例:
[@injectable](/user/injectable)
class CounterCubit extends MainCubit<CounterState> {
CounterCubit() : super(const CounterState.initial());
void increment() => emit(state.copyWith(count: state.count + 1));
}
基本用法
1. 设置依赖注入
使用 GetIt
和 Injectable
进行依赖注入:
[@InjectableInit](/user/InjectableInit)()
void configureInjectionApp() {
// 步骤 1: 注册来自 bloc_small 包的核心依赖
getIt.registerCore();
// 步骤 2: 注册您的应用依赖
getIt.init();
}
void main() {
WidgetsFlutterBinding.ensureInitialized();
configureInjectionApp(); // 初始化核心和应用依赖
runApp(MyApp());
}
重要提示:包含 CommonBloc
单例的 RegisterModule
类是必需的。如果不包括此依赖注入设置,您的应用将遇到错误。CommonBloc
由 bloc_small
用于跨应用管理通用功能如加载状态。
确保在运行应用之前调用 configureInjectionApp()
。
2. 定义您的 BLoC
[@injectable](/user/injectable)
class CountBloc extends MainBloc<CountEvent, CountState> {
CountBloc() : super(const CountState.initial()) {
on<Increment>(_onIncrementCounter);
on<Decrement>(_onDecrementCounter);
}
Future<void> _onIncrementCounter(Increment event, Emitter<CountState> emit) async {
await blocCatch(actions: () async {
await Future.delayed(Duration(seconds: 2));
emit(state.copyWith(count: state.count + 1));
});
}
void _onDecrementCounter(Decrement event, Emitter<CountState> emit) {
if (state.count > 0) emit(state.copyWith(count: state.count - 1));
}
}
3. 使用 Freezed 定义事件和状态
abstract class CountEvent extends MainBlocEvent {
const CountEvent._();
}
[@freezed](/user/freezed)
class Increment extends CountEvent with _$Increment {
const factory Increment() = _Increment;
}
[@freezed](/user/freezed)
class Decrement extends CountEvent with _$Decrement {
const factory Decrement() = _Decrement;
}
[@freezed](/user/freezed)
class CountState extends MainBlocState with $CountState {
const factory CountState.initial({[@Default](/user/Default)(0) int count}) = Initial;
}
4. 使用 BaseBlocPageState
创建 StatefulWidget
class MyHomePage extends StatefulWidget {
MyHomePage({required this.title});
final String title;
[@override](/user/override)
MyHomePageState createState() => _MyHomePageState();
}
class MyHomePageState extends BaseBlocPageState<MyHomePage, CountBloc> {
[@override](/user/override)
Widget buildPage(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: BlocBuilder<CountBloc, CountState>(
builder: (context, state) {
return Text(
'${state.count}',
);
},
),
),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
onPressed: () => bloc.add(Increment()),
tooltip: 'Increment',
child: Icon(Icons.add),
),
FloatingActionButton(
onPressed: () => bloc.add(Decrement()),
tooltip: 'decrement',
child: Icon(Icons.remove),
),
],
),
);
}
}
使用 Cubit(替代方法)
如果更喜欢不使用事件的简单方法,可以使用 Cubit 代替 BLoC:
1. 定义您的 Cubit
[@injectable](/user/injectable)
class CounterCubit extends MainCubit<CounterState> {
CounterCubit() : super(const CounterState());
Future<void> increment() async {
await cubitCatch(
actions: () async {
await Future.delayed(Duration(seconds: 1));
emit(state.copyWith(count: state.count + 1));
},
keyLoading: 'increment',
);
}
void decrement() {
if (state.count > 0) {
emit(state.copyWith(count: state.count - 1));
}
}
}
2. 使用 Freezed 定义 Cubit 状态
[@freezed](/user/freezed)
class CountState extends MainBlocState with $CountState {
const factory CountState.initial({[@Default](/user/Default)(0) int count}) = Initial;
}
3. 使用 BaseCubitPageState
创建 StatefulWidget
class CounterPage extends StatefulWidget {
const CounterPage({super.key});
[@override](/user/override)
State<CounterPage> createState() => _CounterPageState();
}
class _CounterPageState extends BaseCubitPageState<CounterPage, CountCubit> {
[@override](/user/override)
Widget buildPage(BuildContext context) {
return buildLoadingOverlay(
loadingKey: 'increment',
child: Scaffold(
appBar: AppBar(title: const Text('Counter Example')),
body: Center(
child: BlocBuilder<CountCubit, CountState>(
builder: (context, state) {
return Text(
'${state.count}',
style: const TextStyle(fontSize: 48),
);
},
),
),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
onPressed: () => bloc.increment(),
child: const Icon(Icons.add),
),
const SizedBox(height: 16),
FloatingActionButton(
onPressed: () => bloc.decrement(),
child: const Icon(Icons.remove),
),
],
),
),
);
}
}
使用 StatelessWidget
bloc_small
也支持 StatelessWidget
,具有与 StatefulWidget
类似的功能。
1. 使用 BLoC 与 StatelessWidget
class MyHomePage extends BaseBlocPage<CountBloc> {
const MyHomePage({super.key});
[@override](/user/override)
Widget buildPage(BuildContext context, CountBloc bloc) {
return buildLoadingOverlay(
context,
child: Scaffold(
appBar: AppBar(title: const Text('Counter Example')),
body: Center(
child: BlocBuilder<CountBloc, CountState>(
builder: (context, state) {
return Text(
'${state.count}',
style: const TextStyle(fontSize: 48),
);
},
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => bloc.add(const Increment()),
child: const Icon(Icons.add),
),
),
);
}
}
2. 使用 Cubit 与 StatelessWidget
class CounterPage extends BaseCubitPage<CountCubit> {
const CounterPage({super.key});
[@override](/user/override)
Widget buildPage(BuildContext context, CountCubit cubit) {
return buildLoadingOverlay(
context,
child: Scaffold(
appBar: AppBar(title: const Text('Counter Example')),
body: Center(
child: BlocBuilder<CountCubit, CountState>(
builder: (context, state) {
return Text(
'${state.count}',
style: const TextStyle(fontSize: 48),
);
},
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => cubit.increment(),
child: const Icon(Icons.add),
),
),
);
}
}
StatelessWidget 实现的关键特性
特性 | 描述 |
---|---|
基础类 | BaseBlocPage 和 BaseCubitPage |
依赖注入 | 自动依赖注入 |
加载管理 | 内置加载覆盖支持 |
导航 | 集成导航能力 |
状态管理 | 完整的 BLoC/Cubit 模式支持 |
StatelessWidget 与 StatefulWidget 的选择
使用场景 | 小部件类型 |
---|---|
简单 UI 而没有本地状态 | StatelessWidget |
复杂 UI 且具有本地状态 | StatefulWidget |
性能关键屏幕 | StatelessWidget |
具有生命周期需求的屏幕 | StatefulWidget |
如果想使用 Auto Route 集成
- 添加
auto_route
到依赖项:
dependencies:
auto_route:
dev_dependencies:
auto_route_generator:
- 创建路由:
[@AutoRouterConfig](/user/AutoRouterConfig)()
[@LazySingleton](/user/LazySingleton)()
class AppRouter extends BaseAppRouter {
[@override](/user/override)
List<AutoRoute> get routes => [
AutoRoute(page: HomeRoute.page, initial: true),
AutoRoute(page: SettingsRoute.page),
];
}
- 在依赖注入中注册路由
void configureInjectionApp() {
// 注册 AppRouter(推荐)
getIt.registerAppRouter<AppRouter>(AppRouter(), enableNavigationLogs: true);
// 注册其他依赖
getIt.registerCore();
getIt.init();
}
- 配置
MaterialApp
使用auto_route
class MyApp extends StatelessWidget {
final _router = getIt<AppRouter>();
[@override](/user/override)
Widget build(BuildContext context) {
return MaterialApp.router(
routerConfig: _router.config(),
// ... 其他 MaterialApp 属性
);
}
}
- 导航
class MyWidget extends StatelessWidget {
final navigator = getIt.getNavigator();
void _onNavigate() {
navigator?.push(const HomeRoute());
}
}
使用 bloc
和 cubit
提供的 AppNavigator
:
class _MyWidgetState extends BaseBlocPageState<MyWidget, MyWidgetBloc> {
void _onNavigate() {
navigator?.push(const HomeRoute());
}
}
class _MyWidgetState extends BaseCubitPageState<MyWidget, MyWidgetCubit> {
void _onNavigate() {
navigator?.push(const HomeRoute());
}
}
// 基本导航
navigator?.push(const HomeRoute());
// 带参数的导航
navigator?.push(UserRoute(userId: 123));
最佳实践
- 始终在 DI 设置中注册
AppRouter
- 使用
AppNavigator
提供的类型安全方法 - 处理可能的初始化错误
- 对于复杂应用,考虑创建导航服务类
特点
- 类型安全路由
- 自动路由生成
- 平台自适应过渡
- 深度链接支持
- 嵌套导航
- 与依赖注入集成
优势
- 编译时路由验证
- 清洁一致的导航 API
- 减少样板代码
- 更好的开发体验
- 与
bloc_small
包轻松集成
对于更复杂的导航场景和详细文档,请参阅 auto_route
文档。
高级用法
处理加载状态
bloc_small
提供了一个方便的方法来管理加载状态并显示加载指示器,使用 CommonBloc
和 buildLoadingOverlay
方法。
使用 buildLoadingOverlay
当使用 BaseBlocPageState
时,您可以轻松地向整个页面添加加载覆盖:
class MyHomePageState extends BaseBlocPageState<MyHomePage, CountBloc> {
[@override](/user/override)
Widget buildPage(BuildContext context) {
return buildLoadingOverlay(
child: Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('You have pushed the button this many times:'),
BlocBuilder<CountBloc, CountState>(
builder: (context, state) {
return Text('${state.count}');
},
)
],
),
),
floatingActionButton: Wrap(
spacing: 5,
children: [
FloatingActionButton(
onPressed: () => bloc.add(Increment()),
tooltip: 'Increment',
child: Icon(Icons.add),
),
FloatingActionButton(
onPressed: () => bloc.add(Decrement()),
tooltip: 'decrement',
child: Icon(Icons.remove),
),
],
),
),
);
}
}
buildLoadingOverlay
方法会包装您的页面内容,并在加载状态活动时自动显示加载指示器。
自定义加载覆盖
您可以自定义加载覆盖,通过提供 loadingWidget
并指定 loadingKey
:
buildLoadingOverlay(
child: YourPageContent(),
loadingWidget: YourCustomLoadingWidget(),
loadingKey:'customLoadingKey'
)
激活加载状态
要显示或隐藏加载覆盖,请在 BLoC 中使用 showLoading
和 hideLoading
方法:
class YourBloc extends MainBloc<YourEvent, YourState> {
Future<void> someAsyncOperation() async {
showLoading(); // 或 showLoading(key: 'customLoadingKey');
try {
// 执行异步操作
} finally {
hideLoading(); // 或 hideLoading(key: 'customLoadingKey');
}
}
}
这种方法提供了干净一致的方式来处理整个应用程序中的加载状态,具有使用全局或组件特定加载指示器的灵活性。
错误处理
使用 blocCatch
方法在 BLoC 中处理错误:
await blocCatch(
actions: () async {
// 异步逻辑
throw Exception('Something went wrong');
},
onError: (error) {
// 处理错误
print('Error occurred: $error');
}
);
ReactiveSubject
使用 ReactiveSubject
ReactiveSubject
是一个功能强大的流控制器,结合了 BehaviorSubject
的功能和额外的反应式操作符。
核心功能
1. 值管理
// 使用初始值创建
final subject = ReactiveSubject<int>(initialValue: 0);
// 创建广播主题
final broadcast = ReactiveSubject<int>.broadcast(initialValue: 0);
// 添加值
subject.add(1);
// 获取当前值
print(subject.value);
// 检查是否关闭
print(subject.isClosed);
// 完成后清理
subject.dispose();
2. 流变换
map
- 转换每个值
final celsius = ReactiveSubject<double>();
final fahrenheit = celsius.map((c) => c * 9/5 + 32);
where
- 过滤值
final numbers = ReactiveSubject<int>();
final evenNumbers = numbers.where((n) => n % 2 == 0);
switchMap
- 切换到新流
final searchQuery = ReactiveSubject<String>();
final results = searchQuery.switchMap((query) => performSearch(query)); // 取消之前的搜索
debounceTime
- 延迟发射
final input = ReactiveSubject<String>();
final debouncedInput = input.debounceTime(Duration(milliseconds: 300));
throttleTime
- 速率限制发射
final clicks = ReactiveSubject<void>();
final throttledClicks = clicks.throttleTime(Duration(seconds: 1));
distinct
- 仅在值改变时发射
final values = ReactiveSubject<String>();
final distinctValues = values.distinct();
高级操作
withLatestFrom
- 结合另一个流
final main = ReactiveSubject<int>();
final other = ReactiveSubject<String>();
final combined = main.withLatestFrom(other,
(int a, String b) => '$a-$b');
startWith
- 开始时带有值
final subject = ReactiveSubject<int>();
final withDefault = subject.startWith(0);
scan
- 累加值
final prices = ReactiveSubject<double>();
final total = prices.scan<double>(
0.0,
(sum, price, _) => sum + price,
);
doOnData/doOnError
- 侧效果
final subject = ReactiveSubject<int>();
final withLogging = subject
.doOnData((value) => print('Emitted: $value'))
.doOnError((error, _) => print('Error: $error'));
静态操作符
combineLatest
- 组合多个主题
final subject1 = ReactiveSubject<int>();
final subject2 = ReactiveSubject<String>();
final combined = ReactiveSubject.combineLatest([subject1, subject2]);
merge
- 合并多个主题
final subject1 = ReactiveSubject<int>();
final subject2 = ReactiveSubject<int>();
final merged = ReactiveSubject.merge([subject1, subject2]);
实际示例在 BLoC 中
class SearchBloc extends MainBloc<SearchEvent, SearchState> {
final ReactiveSubject<String> _searchQuery = ReactiveSubject<String>();
late final ReactiveSubject<List<String>> _searchResults;
SearchBloc() : super(const SearchState.initial()) {
_searchResults = _searchQuery
.debounceTime(Duration(milliseconds: 100))
.doOnData((query) {
showLoading(key: 'search');
})
.switchMap((query) => _performSearch(query))
.doOnData((query) => hideLoading(key: 'search'));
_searchResults.stream.listen((results) {
add(UpdateResults(results));
});
on<UpdateQuery>(_onUpdateQuery);
on<UpdateResults>(_onUpdateResults);
on<SearchError>(_onSearchError);
}
Future<void> _onUpdateQuery(
UpdateQuery event, Emitter<SearchState> emit) async {
await blocCatch(
keyLoading: 'search',
actions: () async {
await Future.delayed(Duration(seconds: 2));
_searchQuery.add(event.query);
});
}
void _onUpdateResults(UpdateResults event, Emitter<SearchState> emit) {
emit(SearchState.loaded(event.results));
}
void _onSearchError(SearchError event, Emitter<SearchState> emit) {
emit(SearchState.error(event.message));
}
Stream<List<String>> _performSearch(String query) {
final resultSubject = ReactiveSubject<List<String>>();
Future.delayed(Duration(seconds: 1)).then((_) {
if (query.isEmpty) {
resultSubject.add([]);
} else {
resultSubject.add(['Result 1 for "$query"', 'Result 2 for "$query"']);
}
}).catchError((error) {
add(SearchError(error.toString()));
});
return resultSubject.stream;
}
[@override](/user/override)
Future<void> close() {
_searchQuery.dispose();
_searchResults.dispose();
return super.close();
}
}
错误处理
// 添加错误
subject.addError('Something went wrong');
// 在流中处理错误
subject.stream.listen(
(data) => print('Data: $data'),
onError: (error) => print('Error: $error'),
);
// 使用 fromFutureWithError
final subject = ReactiveSubject.fromFutureWithError(
Future.delayed(Duration(seconds: 1)),
onError: (error) => print('Error: $error'),
onFinally: () => print('Completed'),
timeout: Duration(seconds: 5),
);
更多关于Flutter轻量级状态管理插件bloc_small的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter轻量级状态管理插件bloc_small的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,下面是一个关于如何在Flutter项目中使用轻量级状态管理插件bloc_small
的代码示例。bloc_small
是一个简化的BLoC(Business Logic Component)状态管理库,旨在提供比官方bloc
库更轻量级的实现。
首先,确保你已经在pubspec.yaml
文件中添加了bloc_small
依赖:
dependencies:
flutter:
sdk: flutter
bloc_small: ^最新版本号 # 请替换为最新的版本号
然后运行flutter pub get
来安装依赖。
创建一个简单的计数器应用
1. 定义State和Event
首先,我们需要定义状态(State)和事件(Event)。
// counter_event.dart
import 'package:bloc_small/bloc_small.dart';
class IncrementEvent extends Event {}
class DecrementEvent extends Event {}
// counter_state.dart
class CounterState {
final int count;
CounterState({required this.count});
CounterState copyWith({int? count}) {
return CounterState(count: count ?? this.count);
}
}
2. 创建Bloc
接下来,我们创建一个Bloc来处理事件并更新状态。
// counter_bloc.dart
import 'package:bloc_small/bloc_small.dart';
import 'counter_event.dart';
import 'counter_state.dart';
class CounterBloc extends Bloc<CounterEvent, CounterState> {
CounterBloc() : super(CounterState(count: 0));
@override
void onEvent(CounterEvent event, Emitter<CounterState> emit) {
if (event is IncrementEvent) {
emit(state.copyWith(count: state.count + 1));
} else if (event is DecrementEvent) {
emit(state.copyWith(count: state.count - 1));
}
}
}
3. 创建UI组件
现在,我们可以创建一个Flutter组件来使用这个Bloc。
// main.dart
import 'package:flutter/material.dart';
import 'package:bloc_small/bloc_small.dart';
import 'counter_bloc.dart';
import 'counter_event.dart';
import 'counter_state.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('Counter App')),
body: BlocProvider<CounterBloc, CounterState>(
create: (_) => CounterBloc(),
child: CounterPage(),
),
),
);
}
}
class CounterPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final counterBloc = context.bloc<CounterBloc>();
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'${counterBloc.state.count}',
style: Theme.of(context).textTheme.headline4,
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () => counterBloc.add(IncrementEvent()),
child: Text('Increment'),
),
SizedBox(height: 10),
ElevatedButton(
onPressed: () => counterBloc.add(DecrementEvent()),
child: Text('Decrement'),
),
],
),
);
}
}
总结
上述代码展示了如何使用bloc_small
插件在Flutter应用中实现一个简单的计数器。我们定义了事件和状态,创建了一个Bloc来处理事件并更新状态,并在UI组件中使用了Bloc来显示和更新计数器的值。
这个示例提供了一个基本的框架,你可以根据具体需求扩展和修改。