Hooked_Bloc은 훅을 활용하여 Bloc 및 Cubit 클래스의 주입과 사용을 간소화하는 Flutter 패키지입니다. 개발자들은 간결하고 반응형 구문을 사용하여 위젯 내에서 상태 관리 로직에 직접 접근할 수 있어 봉쇄 코드를 줄이고 코드 가독성을 향상시킵니다. Flutter의 현대적인 위젯 모델과 원활하게 통합되며, Android, iOS, Web, macOS, Windows, Linux 등 모든 주요 플랫폼을 지원합니다. Bloc/Cubit의 강력한 기능과 훅의 단순함을 결합함으로써 개발자의 생산성을 높입니다.
사용 사례
복잡한 UI에서 상태 관리 단순화
Bloc/Cubit 사용 시 봇러플레이트 줄이기
훅을 사용하여 반응형 위젯 업데이트 활성화
상태 클래스에 대한 종속성 주입을 간소화하기
확장 가능한 Flutter 애플리케이션 빌드
주요 기능
내장된 훅 기반의 Bloc/Cubit 접근
상태 관리용 최소한의 보일러플레이트
Flutter 위젯 트리와의 원활한 통합
모든 플러터 플랫폼을 지원합니다
상태 처리를 위한 깔끔하고 가독성 높은 구문
적합한 대상
Bloc/Cubit 패턴을 사용하는 개발자
더 깔끔한 상태 관리를 원하는 팀
반응형 UI 업데이트가 필요한 프로젝트
다중 플랫폼을 대상으로 하는 Flutter 앱
기능형 위젯 설계를 수용하는 개발자
고려 사항
Flutter hooks에 익숙해야 합니다
매우 간단한 상태 요구 사항에는 적합하지 않을 수 있습니다
다른 상태 관리 기법과 함께 사용할 때 가장 효과적입니다
비-훅 기반 위젯 아키텍처와 호환되지 않습니다
Bloc/Cubit 인스턴스의 올바른 설정에 따라 달라집니다
원본 README
연결된 소스 저장소의 README 원문 스냅샷입니다. 최신 내용은 저장소에서 확인하세요.
Hooked Bloc
Flutter package that simplifies injection and usage of Bloc/Cubit.
The library is based on the concept of hooks originally introduced in React Native and adapted to Flutter.
Flutter hooks allow you to extract view's logic into common
use cases and reuse them, which makes writing widgets faster and easier.
When you want to use Bloc/Cubit in your application you have to provide an instance of the object down the widgets tree
for state receivers. This is mostly achieved by BlocBuilder along with BlocProvider and enlarges complexity of the
given widget.
Each time you have to use BlocBuilder, BlocListener or BlocSelector. What if we could use the power of Flutter
hooks?
@overrideWidgetbuild(BuildContext context) {
final cubit = useBloc<RealLifeCubit>();
useBlocListener<RealLifeCubit, BuildState>(cubit, (cubit, value, context) {
// Show some view on event
}, listenWhen: (state) => state isErrorState);
final state = useBlocBuilder(
cubit,
buildWhen: (state) =>
[LoadedState, LoadingState, ShowItemState].contains(
state.runtimeType,
),
);
return// Build your widget using `state`
}
This code is functionally equivalent to the previous example. It still rebuilds the widget in the proper way and the
right time. Whole logic of finding adequate Cubit/Bloc and providing current state is hidden in useBloc
and useBlocBuilder hooks.
After that you can (it's optional) initialize the HookedBloc:
voidmain() async {
// With GetIt or InjectableawaitconfigureDependencies();
runApp(
HookedBlocConfigProvider(
injector: () => getIt.get,
builderCondition: (state) => state != null, // Global build condition
listenerCondition: (state) => state != null, // Global listen condition
child: constMyApp(),
)
);
// Or you can omit HookedBlocInjector(...)// and allow library to find the cubit in the widget tree
}
Then you can simply start writing your widget with hooks
// Remember to inherit from HookWidgetclassMyAppextendsHookWidget{
constMyApp({Key? key}) : super(key: key);
@overrideWidgetbuild(BuildContext context) {
// At start obtain a cubit instancefinal cubit = useBloc<CounterCubit>();
// Then observe state's updates// `buildWhen` param will override builderCondition locallyfinal state = useBlocBuilder(cubit, buildWhen: (state) => state <= 10);
// Create a listener for the side-effectuseBlocListener(cubit, (cubit, value, context) {
ScaffoldMessenger.of(context).showSnackBar(
constSnackBar(content: Text("Button clicked"),
));
});
// Build widget's tree without BlocProviderreturnMaterialApp(
home: Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () => cubit.increment(), // Access cubit in tree
child: constIcon(Icons.add),
),
// Consume state without BlocBuilder
body: Center(child: Text("The button has been pressed $state times")),
),
);
}
}
Basics
Existing hooks
Hooked Bloc already comes with a few reusable hooks:
Name
Description
useBloc
Returns required Cubit/Bloc
useBlocFactory
Returns expected Cubit/Bloc by creating it via provided factory
useBlocBuilder
Returns current Cubit/Bloc state - similar to BlocBuilder
useBlocComparativeBuilder
Returns current Cubit/Bloc state basing on comparison result
useBlocListener
Invokes callback - similar to BlocListener
useBlocComparativeListener
Invokes callback basing on state comparison result
useActionListener
Invokes callback, but independent of Bloc/Cubit state
useBloc
useBloc hook tries to find Cubit using the cubit provider, or - if not specified - looks into the widget tree.
@overrideWidgetbuild(BuildContext context) {
// The hook will provide the expected objectfinal cubit = useBloc<SimpleCubit>(
// For default hook automatically closes cubit
closeOnDispose: true,
);
return// Access provided cubit
}
useBlocFactory
useBlocFactory hook tries to find factory using provided injection method and then returns cubit created by it
classSimpleCubitFactoryextendsBlocFactory<SimpleCubit> {
bool _value = true;
@overrideSimpleCubitcreate() {
return _value ? SimpleCubitA() : SimpleCubitB();
}
// This is example method which you can add to configure your cubit creation on runvoidconfigure(bool value) {
_value = value;
}
}
@overrideWidgetbuild(BuildContext context) {
// The hook will provide the expected objectfinal cubit = useBlocFactory<SimpleCubit, SimpleCubitFactory>(
onCubitCreate: (cubitFactory) {
cubitFactory.configure(false);
}
);
return// Access provided cubit
}
useBlocBuilder
useBlocBuilder hook rebuilds the widget when new state appears
finalCounterCubit cubit = CounterCubit("My cubit");
@overrideWidgetbuild(BuildContext context) {
// The state will be updated along with the widget// For default the state will be updated basing on `builderCondition`finalint state = useBlocBuilder(cubit);
return// Access provided state
}
useBlocComparativeBuilder
useBlocComparativeBuilder hook rebuilds the widget for a new state and positive comparison result
finalCounterCubit cubit = CounterCubit("My cubit");
@overrideWidgetbuild(BuildContext context) {
// The state will be updated along with the widget// We can compare state's changes to allow rebuildfinal state = useBlocComparativeBuilder(
cubit,
buildWhen: (int previous, int current) {
return current != previous;
},
);
return// Access provided state
}
useBlocListener
useBlocListener hook allows to observe cubit's states that represent action (e.g. show Snackbar)
finalEventCubit cubit = EventCubit();
@overrideWidgetbuild(BuildContext context) {
// Handle state as event independently of the view stateuseBlocListener(cubit, (_, value, context) {
_showMessage(context, (value asShowMessage).message);
}, listenWhen: (state) => state isShowMessage);
return// Build your widget
}
useBlocComparativeListener
useBlocComparativeListener hook allows to observe and compare cubit's states that represent action (e.g. show Snackbar)
finalEventCubit cubit = EventCubit();
@overrideWidgetbuild(BuildContext context) {
// Handle state as event independently of the view state// We can compare state changes to allow listener function to be calleduseBlocComparativeListener(
cubit,
(_, value, context) {
_showMessage(context, (value asShowMessage).message);
},
listenWhen: (previousState, currentState) => previousState is! ShowMessage && currentState isShowMessage,
);
return// Build your widget
}
useActionListener
useActionListener hook is similar to the useBlocListener but listens to the stream different than state's stream
and can be used for actions that require a different flow of notifying.
Because of that your bloc/cubit must use BlocActionMixin
classMessageActionCubitextendsEventCubitwithBlocActionMixin<String, BuildState> {
// The method used to publish events@overridevoiddispatch(String action) {
super.dispatch(action);
}
}
Then, consume results as you would do with useBlocListener
@overrideWidgetbuild(BuildContext context) {
// Handle separate action stream with values other than a state typeuseActionListener(
cubit,
(String action) {
_showMessage(context, action);
},
// If you need, you can filter actions
actionWhen: (previousAction, action) => true,
);
return// Build your widget
}
Instead of BlocActionMixin you can use one of our classes: ActionCubit or ActionBloc
classMessageActionCubitextendsActionCubit<BuildState, String> {
// The method used to publish events@overridevoiddispatch(String action) {
super.dispatch(action);
}
}
classMessageActionBlocextendsActionBloc<BuildState, BlocEvent, String> {
// The method used to publish events@overridevoiddispatch(String action) {
super.dispatch(action);
}
}
Contribution
We accept any contribution to the project!
Suggestions of a new feature or fix should be created via pull-request or issue.
feature request:
Check if feature is already addressed or declined
Describe why this is needed
Just create an issue with label enhancement and descriptive title. Then, provide a description and/or example code.
This will help the community to understand the need for it.
Write tests for your hook
The test is the best way to explain how the proposed hook should work. We demand a complete test before any code is
merged in order to ensure cohesion with existing codebase.
Add it to the README and write documentation for it
Add a new hook to the existing hooks table and append sample code with usage.
Fix
Check if bug was already found
Describe what is broken
The minimum requirement to report a bug fix is a reproduction path. Write steps that should be followed to find a
problem in code. Perfect situation is when you give full description why some code doesn't work and a solution code.
Write tests for your hook
The test should show that your fix corrects the problem. You can start with straightforward test and then think about
potential edge cases or other places that can be broken.
Add it to the README and write documentation for it
If your fix changed behavior of the library or requires any other extra steps from user, this should be fully
described in README.