Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Filter for actions #82

Merged
merged 3 commits into from
Jan 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 1.5.0

- Allow filter actions in Bloc using actionWhen in useActionListener

## 1.4.4

- Allow put void type to generic type
Expand Down
13 changes: 9 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ This code is functionally equivalent to the previous example. It still rebuilds
right time. Whole logic of finding adequate Cubit/Bloc and providing current state is hidden in `useBloc`
and `useBlocBuilder` hooks.

Full example can be found in <a href="https://github.com/Iteo/hooked_bloc/tree/develop/example">here</a>
Full example can be found in <a href="https://github.com/Iteo/hooked_bloc/tree/main/example">here</a>

## Setup

Expand Down Expand Up @@ -391,9 +391,14 @@ Then, consume results as you would do with `useBlocListener`
@override
Widget build(BuildContext context) {
// Handle separate action stream with values other than a state type
useActionListener(cubit, (String action) {
_showMessage(context, action);
});
useActionListener(
cubit,
(String action) {
_showMessage(context, action);
},
// If you need, you can filter actions
actionWhen: (previousAction, action) => true,
);

return // Build your widget
}
Expand Down
4 changes: 2 additions & 2 deletions lib/src/bloc/action_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
/// }
/// }
///```
abstract class ActionBloc<STATE, EVENT, ACTION>
extends Bloc<EVENT, STATE> with BlocActionMixin<ACTION, STATE> {
abstract class ActionBloc<STATE, EVENT, ACTION> extends Bloc<EVENT, STATE>
with BlocActionMixin<ACTION, STATE> {
ActionBloc(super.initialState);
}
13 changes: 10 additions & 3 deletions lib/src/bloc_action_listener.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,21 @@ import 'package:hooked_bloc/hooked_bloc.dart';

typedef OnActionCallback<T> = Function(T action);

typedef ActionListenerCondition<T> = bool Function(T? previousAction, T action);

/// Calls callback function [onAction] each time, when new action is dispatched from [BlocBase] with [BlocActionMixin], [ActionCubit] or [ActionBloc]
void useActionListener<ACTION>(
BlocActionMixin<ACTION, Object> actionMixin,
OnActionCallback<ACTION> onAction,
) {
OnActionCallback<ACTION> onAction, {
ActionListenerCondition<ACTION>? actionWhen,
}) {
useEffect(
() {
final subscription = actionMixin.actions.listen(onAction);
final subscription = actionMixin.actions.listen((action) {
if(actionWhen == null || actionWhen(actionMixin.previousAction, action)) {
onAction(action);
}
});
return subscription.cancel;
},
[actionMixin],
Expand Down
16 changes: 16 additions & 0 deletions lib/src/mixins/bloc_action_mixin.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,24 @@ import 'package:meta/meta.dart';
/// ...
/// }
/// ```
///
/// You can also filter your actions using actionWhen.
/// ```dart
/// useActionListener(
/// cubit,
/// (String action) {
/// //do sth with filtered action
/// _showMessage(context, action);
/// },
/// actionWhen: (previousAction, action) => true,
/// });
/// ```
/// See also [ActionCubit] and [ActionBloc]
mixin BlocActionMixin<ACTION, S> on BlocBase<S> {
final _streamController = StreamController<ACTION>.broadcast();

ACTION? previousAction;

Stream<ACTION> get actions => _streamController.stream;

@visibleForTesting
Expand All @@ -44,10 +58,12 @@ mixin BlocActionMixin<ACTION, S> on BlocBase<S> {
@protected
void dispatch(ACTION action) {
_streamController.add(action);
previousAction = action;
}

@override
Future<void> close() async {
previousAction = null;
await Future.wait([
super.close(),
_streamController.close(),
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: hooked_bloc
description: Flutter package that simplifies injection and usage of Bloc/Cubit.
version: 1.4.4
version: 1.5.0
repository: https://github.com/Iteo/hooked_bloc
issue_tracker: https://github.com/Iteo/hooked_bloc/issues
homepage: https://github.com/Iteo/hooked_bloc
Expand Down
58 changes: 58 additions & 0 deletions test/bloc_action_listener_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ class CounterActionCubit extends ActionCubit<STATE, ACTION> {
void dispatchAction() {
dispatch(state.toString());
}

void dispatchActionValue(String value) {
dispatch(value);
}
}

void main() {
Expand Down Expand Up @@ -83,5 +87,59 @@ void main() {
expect(listenerCalls, i);
}
});

testWidgets(
'when Cubit dispatch multiple times, actionWhen should be called only with 2 chars',
(tester) async {
int listenerCalls = 0;

Widget Function(BuildContext) builder(
ActionCubit<STATE, ACTION> cubit,
) {
return (context) {
useActionListener(
cubit,
(ACTION action) {
listenerCalls++;
},
actionWhen: (previousAction, action) => action.length == 2
);

return Container();
};
}

CounterActionCubit cubit = CounterActionCubit();

HookBuilder hookWidget = HookBuilder(builder: builder(cubit));

cubit.dispatchActionValue('1');
await tester.pumpWidget(hookWidget);
expect(listenerCalls, 0);

cubit.dispatchActionValue('11');
await tester.pumpWidget(hookWidget);
expect(listenerCalls, 1);

cubit.dispatchActionValue('2');
await tester.pumpWidget(hookWidget);
expect(listenerCalls, 1);

cubit.dispatchActionValue('22');
await tester.pumpWidget(hookWidget);
expect(listenerCalls, 2);

cubit.dispatchActionValue('333');
await tester.pumpWidget(hookWidget);
expect(listenerCalls, 2);

cubit.dispatchActionValue('4');
await tester.pumpWidget(hookWidget);
expect(listenerCalls, 2);

cubit.dispatchActionValue('55');
await tester.pumpWidget(hookWidget);
expect(listenerCalls, 3);
});
});
}