Skip to content

Commit

Permalink
Revert "Implement bloc presentation listener without hooks"
Browse files Browse the repository at this point in the history
This reverts commit 29b72c2.
  • Loading branch information
KamilSztandur committed Oct 3, 2023
1 parent 29b72c2 commit 2ccddfb
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 150 deletions.
1 change: 1 addition & 0 deletions packages/bloc_presentation/lib/bloc_presentation.dart
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export 'src/bloc_presentation_listener.dart';
export 'src/bloc_presentation_mixin.dart';
export 'src/use_bloc_presentation_listener.dart';
145 changes: 25 additions & 120 deletions packages/bloc_presentation/lib/src/bloc_presentation_listener.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import 'dart:async';

import 'package:bloc_presentation/src/use_bloc_presentation_listener.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:nested/nested.dart';
import 'package:provider/provider.dart';

import 'bloc_presentation_mixin.dart';

Expand All @@ -14,133 +14,38 @@ typedef BlocPresentationWidgetListener<P> = void Function(
P event,
);

/// A widget that listens to events from a Bloc and invokes a listener
/// function in response to new events.
///
/// This widget is used to interact with [BlocPresentationMixin] and listen
/// to events of type [P]. When a new event of type [P] is emitted by the Bloc,
/// the provided [listener] function is called with the current [BuildContext]
/// and the event itself.
///
/// Example:
/// ```dart
/// BlocPresentationListener<MyBloc, MyEvent>(
/// listener: (context, event) {
/// // Handle the event here
/// },
/// bloc: myBloc, // You don't have to pass it if you provided it in context
/// child: SomeWidget(),
/// )
/// ```
/// {@template bloc_presentation_listener}
/// Widget that listens to new presentation events in a specified [bloc].
/// {@endtemplate}
class BlocPresentationListener<B extends BlocPresentationMixin<dynamic, P>, P>
extends SingleChildStatefulWidget {
/// Creates a [BlocPresentationListener].
///
/// The [listener] function is required and will be called with the
/// current [BuildContext] and the event of type [P] when new events are
/// emitted by the Bloc.
///
/// The [bloc] parameter is optional and can be used to specify the
/// Bloc to listen to. If not provided, the nearest ancestor Bloc of
/// type [B] in the widget tree will be used.
///
/// The [child] parameter is optional and represents the child widget
/// to display. If not provided, an empty [SizedBox] is used as the child.
extends SingleChildStatelessWidget {
/// {@macro bloc_presentation_listener}
const BlocPresentationListener({
super.key,
required this.listener,
this.bloc,
this.child,
}) : super(child: child);

/// A function that defines the behavior when a new event of type [P] is
/// emitted by the Bloc. It takes the current [BuildContext] and the
/// event itself as parameters and is responsible for handling the event.
final BlocPresentationWidgetListener<P> listener;
required this.listener,
super.child,
});

/// The Bloc from which to listen to events of type [P]. If not provided,
/// the nearest ancestor Bloc of type [B] in the widget tree will be used.
/// The [bloc] that the [BlocPresentationListener] will interact with.
/// If omitted, [BlocPresentationListener] will automatically perform a lookup using
/// [Provider] and the current `BuildContext`.
final B? bloc;

/// An optional child widget to display within the [BlocPresentationListener].
/// If not provided, an empty [SizedBox] is used as the child.
final Widget? child;

@override
SingleChildState<BlocPresentationListener<B, P>> createState() =>
_BlocPresentationListenerBaseState<B, P>();
}

class _BlocPresentationListenerBaseState<
B extends BlocPresentationMixin<dynamic, P>,
P> extends SingleChildState<BlocPresentationListener<B, P>> {
StreamSubscription<P>? _streamSubscription;
late B _bloc;

@override
void initState() {
super.initState();

_bloc = _bloc = widget.bloc ?? context.read<B>();

_streamSubscription = _bloc.presentation.listen(
(event) => widget.listener(context, event),
);
}

@override
void didUpdateWidget(BlocPresentationListener<B, P> oldWidget) {
super.didUpdateWidget(oldWidget);

final oldBloc = oldWidget.bloc ?? context.read<B>();
final currentBloc = widget.bloc ?? oldBloc;

if (oldBloc != currentBloc) {
if (_streamSubscription != null) {
_unsubscribe();
_bloc = currentBloc;
}

_subscribe();
}
}

@override
void didChangeDependencies() {
super.didChangeDependencies();

final bloc = widget.bloc ?? context.read<B>();

if (_bloc != bloc) {
if (_streamSubscription != null) {
_unsubscribe();
_bloc = bloc;
}

_subscribe();
}
}
/// The [BlocPresentationWidgetListener] which will be called on every new presentation event.
final BlocPresentationWidgetListener<P> listener;

@override
Widget buildWithChild(BuildContext context, Widget? child) {
return child ?? const SizedBox();
}

@override
void dispose() {
_unsubscribe();

super.dispose();
}

void _subscribe() {
_streamSubscription = _bloc.presentation.listen(
(event) => widget.listener(context, event),
return HookBuilder(
builder: (context) {
useBlocPresentationListener(
listener: listener,
bloc: bloc,
);

return child ?? const SizedBox();
},
);
}

void _unsubscribe() {
_streamSubscription?.cancel();
_streamSubscription = null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import 'package:bloc_presentation/bloc_presentation.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:provider/provider.dart';

/// Subscribes to the presentation stream and invokes `listener` on each event.
///
/// If [bloc] is omitted, [useBlocPresentationListener] will automatically perform
/// a lookup using [Provider] and the current `BuildContext`.
void useBlocPresentationListener<B extends BlocPresentationMixin<dynamic, P>,
P>({
required BlocPresentationWidgetListener<P> listener,
B? bloc,
}) {
final context = useContext();
final effectiveStream = bloc?.presentation ??
context.select<B, Stream<P>>((bloc) => bloc.presentation);

useEffect(
() {
final subscription = effectiveStream.listen(
(event) => listener(context, event),
);

return subscription.cancel;
},
[effectiveStream, listener],
);
}
1 change: 1 addition & 0 deletions packages/bloc_presentation/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ dependencies:
flutter:
sdk: flutter
flutter_bloc: ^8.0.0
flutter_hooks: ^0.20.0
nested: ^1.0.0
provider: ^6.0.1

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:bloc_presentation/bloc_presentation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:provider/provider.dart';
Expand Down Expand Up @@ -28,6 +29,7 @@ void main() {
late _TestCubit cubit;
late BlocPresentationWidgetListener<_PresentationEvent> listener;
late _PresentationEvent event;
late HookElement element;

setUpAll(() {
registerFakes();
Expand All @@ -44,10 +46,16 @@ void main() {

testWidgets('Correctly calls the provided listener', (tester) async {
await tester.pumpWidget(
BlocPresentationListener(
bloc: cubit,
listener: listener,
child: const SizedBox(),
HookBuilder(
builder: (context) {
element = context as HookElement;
useBlocPresentationListener(
listener: listener,
bloc: cubit,
);

return Container();
},
),
);

Expand All @@ -56,15 +64,22 @@ void main() {
await tester.pump();

verify(() => listener(any(), event)).called(1);
expect(element.dirty, false);
});

testWidgets('Correctly fallsback to the Provider cubit', (tester) async {
await tester.pumpWidget(
Provider<_TestCubit>.value(
value: cubit,
child: BlocPresentationListener<_TestCubit, _PresentationEvent>(
listener: listener,
child: const SizedBox(),
child: HookBuilder(
builder: (context) {
element = context as HookElement;
useBlocPresentationListener<_TestCubit, _PresentationEvent>(
listener: listener,
);

return Container();
},
),
),
);
Expand All @@ -74,14 +89,20 @@ void main() {
await tester.pump();

verify(() => listener(any(), event)).called(1);
expect(element.dirty, false);
});

testWidgets('Correctly disposes listener', (tester) async {
await tester.pumpWidget(
BlocPresentationListener<_TestCubit, _PresentationEvent>(
bloc: cubit,
listener: listener,
child: const SizedBox(),
HookBuilder(
builder: (context) {
useBlocPresentationListener(
listener: listener,
bloc: cubit,
);

return Container();
},
),
);

Expand All @@ -90,31 +111,39 @@ void main() {
await tester.pump();

await tester.pumpWidget(const SizedBox());

cubit.emitEvent(event);

await tester.pump();

verify(() => listener(any(), event)).called(1);
});

testWidgets('Updates dependencies', (tester) async {
await tester.pumpWidget(
BlocPresentationListener<_TestCubit, _PresentationEvent>(
bloc: cubit,
listener: listener,
child: const SizedBox(),
HookBuilder(
builder: (context) {
useBlocPresentationListener(
listener: listener,
bloc: cubit,
);

return Container();
},
),
);

cubit.emitEvent(event);
await tester.pump();

await tester.pumpWidget(
BlocPresentationListener<_TestCubit, _PresentationEvent>(
bloc: cubit,
listener: listener,
child: const SizedBox(),
HookBuilder(
builder: (context) {
useBlocPresentationListener(
listener: listener,
bloc: cubit,
);

return Container();
},
),
);

Expand All @@ -126,10 +155,15 @@ void main() {
final cubit2 = _TestCubit();

await tester.pumpWidget(
BlocPresentationListener<_TestCubit, _PresentationEvent>(
bloc: cubit2,
listener: listener,
child: const SizedBox(),
HookBuilder(
builder: (context) {
useBlocPresentationListener(
listener: listener,
bloc: cubit2,
);

return Container();
},
),
);

Expand All @@ -142,18 +176,22 @@ void main() {
final listener2 = _MockListener();

await tester.pumpWidget(
BlocPresentationListener<_TestCubit, _PresentationEvent>(
bloc: cubit2,
listener: listener2,
child: const SizedBox(),
HookBuilder(
builder: (context) {
useBlocPresentationListener<_TestCubit, _PresentationEvent>(
listener: listener2,
bloc: cubit2,
);

return Container();
},
),
);

cubit2.emitEvent(event);
await tester.pump();

verifyNever(() => listener(any(), event));

verify(() => listener2(any(), event)).called(1);
});
});
Expand Down

0 comments on commit 2ccddfb

Please sign in to comment.