Flutter自定义Hook管理插件hooked_bloc的使用
Flutter自定义Hook管理插件hooked_bloc的使用
简介
hooked_bloc
是一个Flutter包,简化了Bloc/Cubit的注入和使用。该库基于React Native中引入的钩子概念,并适应Flutter环境。通过Flutter Hooks,您可以将视图逻辑提取到通用用例中并重用它们,从而加快编写小部件的速度并使其更简单。
目录
动机
在应用程序中使用Bloc/Cubit时,您需要在小部件树中提供对象实例以接收状态。这通常通过BlocBuilder
和BlocProvider
实现,增加了小部件的复杂性。每次使用BlocBuilder
、BlocListener
或BlocSelector
时,都会增加代码量和复杂度。借助hooked_bloc
,我们可以通过钩子来简化这些操作。
例如,以下代码:
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: ...,
body: BlocProvider<RealLifeCubit>(
create: (context) => RealLifeCubit()..loadData(),
child: BlocListener<RealLifeCubit, hooked.BuildState>(
listenWhen: (_, state) => state is ErrorState,
listener: (context, state) {
// Show some view on event
},
child: BlocBuilder<RealLifeCubit, hooked.BuildState>(
buildWhen: (_, state) =>
[LoadedState, LoadingState, ShowItemState]
.contains(state.runtimeType),
builder: (BuildContext context, hooked.BuildState state) {
return // Build your widget using `state`
},
),
),
),
);
}
可以简化为:
@override
Widget build(BuildContext context) {
final cubit = useBloc<RealLifeCubit>();
useBlocListener<RealLifeCubit, BuildState>(cubit, (cubit, value, context) {
// Show some view on event
}, listenWhen: (state) => state is ErrorState);
final state = useBlocBuilder(
cubit,
buildWhen: (state) =>
[LoadedState, LoadingState, ShowItemState].contains(
state.runtimeType,
),
);
return // Build your widget using `state`
}
这段代码功能等同于之前的例子,它仍然会在适当的时间重建小部件。寻找合适的Cubit/Bloc并提供当前状态的全部逻辑都隐藏在useBloc
和useBlocBuilder
钩子中。
安装
安装包
运行命令:
flutter pub add hooked_bloc
或者手动添加依赖项到pubspec.yaml
:
dependencies:
# Library already contains flutter_hooks package
hooked_bloc:
初始化HookedBloc
(可选):
void main() async {
// With GetIt or Injectable
await configureDependencies();
runApp(
HookedBlocConfigProvider(
injector: () => getIt.get,
builderCondition: (state) => state != null, // Global build condition
listenerCondition: (state) => state != null, // Global listen condition
child: const MyApp(),
)
);
// Or you can omit HookedBlocInjector(...)
// and allow library to find the cubit in the widget tree
}
然后你可以开始使用钩子编写小部件:
// Remember to inherit from HookWidget
class MyApp extends HookWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
// At start obtain a cubit instance
final cubit = useBloc<CounterCubit>();
// Then observe state's updates
// `buildWhen` param will override builderCondition locally
final state = useBlocBuilder(cubit, buildWhen: (state) => state <= 10);
// Create a listener for the side-effect
useBlocListener(cubit, (cubit, value, context) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Button clicked"),
));
});
// Build widget's tree without BlocProvider
return MaterialApp(
home: Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () => cubit.increment(), // Access cubit in tree
child: const Icon(Icons.add),
),
// Consume state without BlocBuilder
body: Center(child: Text("The button has been pressed $state times")),
),
);
}
}
基础
钩子列表
名称 | 描述 |
---|---|
useBloc |
返回所需的Cubit/Bloc |
useBlocFactory |
通过提供的工厂创建并返回预期的Cubit/Bloc |
useBlocBuilder |
返回当前的Cubit/Bloc状态,类似于BlocBuilder |
useBlocComparativeBuilder |
根据比较结果返回当前的Cubit/Bloc状态 |
useBlocListener |
调用回调函数,类似于BlocListener |
useBlocComparativeListener |
根据状态比较结果调用回调函数 |
useActionListener |
调用回调函数,但独立于Bloc/Cubit状态 |
useBloc
useBloc
钩子尝试通过Cubit提供者找到Cubit,如果未指定则查找小部件树。
@override
Widget build(BuildContext context) {
// The hook will provide the expected object
final cubit = useBloc<SimpleCubit>(
// For default hook automatically closes cubit
closeOnDispose: true,
);
return // Access provided cubit
}
useBlocFactory
useBlocFactory
钩子尝试通过提供的注入方法找到工厂并返回由其创建的Cubit。
class SimpleCubitFactory extends BlocFactory<SimpleCubit> {
bool _value = true;
@override
SimpleCubit create() {
return _value ? SimpleCubitA() : SimpleCubitB();
}
void configure(bool value) {
_value = value;
}
}
@override
Widget build(BuildContext context) {
// The hook will provide the expected object
final cubit = useBlocFactory<SimpleCubit, SimpleCubitFactory>(
onCubitCreate: (cubitFactory) {
cubitFactory.configure(false);
}
);
return // Access provided cubit
}
useBlocBuilder
useBlocBuilder
钩子在新状态出现时重建小部件。
final CounterCubit cubit = CounterCubit("My cubit");
@override
Widget build(BuildContext context) {
// The state will be updated along with the widget
// For default the state will be updated basing on `builderCondition`
final int state = useBlocBuilder(cubit);
return // Access provided state
}
useBlocComparativeBuilder
useBlocComparativeBuilder
钩子在新状态出现且比较结果为正时重建小部件。
final CounterCubit cubit = CounterCubit("My cubit");
@override
Widget build(BuildContext context) {
// The state will be updated along with the widget
// We can compare state's changes to allow rebuild
final state = useBlocComparativeBuilder(
cubit,
buildWhen: (int previous, int current) {
return current != previous;
},
);
return // Access provided state
}
useBlocListener
useBlocListener
钩子允许观察代表动作的Cubit状态(例如显示Snackbar)。
final EventCubit cubit = EventCubit();
@override
Widget build(BuildContext context) {
// Handle state as event independently of the view state
useBlocListener(cubit, (_, value, context) {
_showMessage(context, (value as ShowMessage).message);
}, listenWhen: (state) => state is ShowMessage);
return // Build your widget
}
useBlocComparativeListener
useBlocComparativeListener
钩子允许观察并比较代表动作的Cubit状态(例如显示Snackbar)。
final EventCubit cubit = EventCubit();
@override
Widget build(BuildContext context) {
// Handle state as event independently of the view state
// We can compare state changes to allow listener function to be called
useBlocComparativeListener(
cubit,
(_, value, context) {
_showMessage(context, (value as ShowMessage).message);
},
listenWhen: (previousState, currentState) => previousState is! ShowMessage && currentState is ShowMessage,
);
return // Build your widget
}
useActionListener
useActionListener
钩子与useBlocListener
类似,但监听不同于状态流的流,并可用于需要不同通知流程的动作。
class MessageActionCubit extends EventCubit with BlocActionMixin<String, BuildState> {
@override
void dispatch(String action) {
super.dispatch(action);
}
}
@override
Widget build(BuildContext context) {
// Handle separate action stream with values other than a state type
useActionListener(
cubit,
(String action) {
_showMessage(context, action);
},
actionWhen: (previousAction, action) => true,
);
return // Build your widget
}
贡献
欢迎对项目进行任何贡献!
- 提交新的功能请求或修复建议应通过pull-request或issue。
- 检查是否已有相同的功能请求或修复建议。
- 描述为什么需要此功能或描述问题所在。
- 编写测试用例。
- 更新README文档。
更多详情请参考贡献指南。
示例代码
以下是hooked_bloc
的一个完整示例demo:
import 'package:example/di/injector.dart';
import 'package:example/page/home_page.dart';
import 'package:flutter/material.dart';
import 'package:hooked_bloc/hooked_bloc.dart';
void main() async {
// With GetIt or Injectable
await configureDependencies();
runApp(
HookedBlocConfigProvider(
injector: () => getIt.get,
builderCondition: (state) => state != null, // Global build condition
listenerCondition: (state) => state != null, // Global listen condition
child: const MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
final appName = "Hooked Bloc";
@override
Widget build(BuildContext context) {
return MaterialApp(
title: appName,
theme: ThemeData(primarySwatch: Colors.blue),
home: HomePage(title: appName),
);
}
}
// Quickstart example counter page
class CounterPage extends HookWidget {
const CounterPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
// At start obtain a cubit instance
final cubit = useBloc<CounterCubit>();
// Then observe state's updates
//`buildWhen` param will override builderCondition locally
final state = useBlocBuilder(cubit, buildWhen: (state) => state <= 10);
// Create a listener for the side-effect
useBlocListener(cubit, (cubit, value, context) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Button clicked")));
});
// Build widget's tree without BlocProvider
return Scaffold(
appBar: AppBar(title: Text("Counter Page")),
floatingActionButton: FloatingActionButton(
onPressed: () => cubit.increment(), // Access cubit in tree
child: const Icon(Icons.add),
),
// Consume state without BlocBuilder
body: Center(child: Text("The button has been pressed $state times")),
);
}
}
这个示例展示了如何使用hooked_bloc
中的各种钩子来简化状态管理和UI更新。希望这能帮助你更好地理解和使用hooked_bloc
!
更多关于Flutter自定义Hook管理插件hooked_bloc的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter自定义Hook管理插件hooked_bloc的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,以下是一个关于如何在Flutter中使用hooked_bloc
插件来管理自定义Hook的示例代码。hooked_bloc
是一个用于在Flutter应用中创建和使用Hook的库,它类似于React中的Hooks,但适用于Flutter。
首先,确保你已经在pubspec.yaml
文件中添加了hooked_bloc
依赖:
dependencies:
flutter:
sdk: flutter
hooked_bloc: ^最新版本号 # 替换为实际版本号
然后,运行flutter pub get
来安装依赖。
1. 创建一个自定义Hook
让我们创建一个简单的自定义Hook,用于管理一个计数器的状态。
// hooks/use_counter.dart
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooked_bloc/hooked_bloc.dart';
part 'use_counter.g.dart'; // 确保生成代码的文件被导入
@HookBloc()
class CounterBloc with _$CounterBloc {
int _count = 0;
void increment() {
_count++;
}
int get count => _count;
}
Hook useCounter() {
return useHookedBloc<CounterBloc>(
() => CounterBloc(),
);
}
运行以下命令来生成Bloc的模板代码:
flutter pub run build_runner build
2. 使用自定义Hook
现在,我们可以在一个Flutter Widget中使用这个自定义Hook。
// main.dart
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'hooks/use_counter.dart';
void main() {
runApp(HookedBlocProvider(
child: MyApp(),
));
}
class MyApp extends HookWidget {
@override
Widget build(BuildContext context) {
final counterBloc = useCounter();
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Counter Hook Demo'),
),
body: 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,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
counterBloc.context.read<CounterBloc>().increment();
},
tooltip: 'Increment',
child: Icon(Icons.add),
),
),
);
}
}
3. 完整项目结构
确保你的项目结构类似于以下:
your_flutter_project/
├── android/
├── ios/
├── lib/
│ ├── hooks/
│ │ ├── use_counter.dart
│ │ └── use_counter.g.dart (自动生成)
│ ├── main.dart
│ └── pubspec.yaml
└── ...
总结
上述代码展示了如何在Flutter中使用hooked_bloc
来创建一个自定义Hook,并在Widget中使用它。这个示例中,我们创建了一个简单的计数器Hook,并在UI中展示了计数器的值,同时提供了一个按钮来增加计数器的值。你可以根据需要扩展这个示例,添加更多的逻辑和功能。