Line data Source code
1 : import 'package:flutter/foundation.dart';
2 : import 'package:flutter/widgets.dart';
3 : import 'package:flutter_hooks_bloc/src/multi_bloc_listener.dart';
4 :
5 : import 'bloc_hook.dart';
6 : import 'flutter_bloc.dart' hide BlocProvider;
7 :
8 : /// Signature for the `listener` function which takes the `BuildContext` along
9 : /// with the `state` and is responsible for executing in response to
10 : /// `state` changes.
11 : typedef BlocWidgetListener<S> = void Function(BuildContext context, S state);
12 :
13 : /// Signature for the `listenWhen` function which takes the previous `state`
14 : /// and the current `state` and is responsible for returning a [bool] which
15 : /// determines whether or not to call [BlocWidgetListener] of [BlocListener]
16 : /// with the current `state`.
17 : typedef BlocListenerCondition<S> = bool Function(S previous, S current);
18 :
19 : /// {@template bloc_listener}
20 : /// Takes a [BlocWidgetListener] and an optional [bloc] and invokes
21 : /// the [listener] in response to `state` changes in the [bloc].
22 : /// It should be used for functionality that needs to occur only in response to
23 : /// a `state` change such as navigation, showing a `SnackBar`, showing
24 : /// a `Dialog`, etc...
25 : /// The [listener] is guaranteed to only be called once for each `state` change
26 : /// unlike the `builder` in `BlocBuilder`.
27 : ///
28 : /// If the [bloc] parameter is omitted, [BlocListener] will automatically
29 : /// perform a lookup using the current `BuildContext`.
30 : ///
31 : /// ```dart
32 : /// BlocListener<BlocA, BlocAState>(
33 : /// listener: (context, state) {
34 : /// // do stuff here based on BlocA's state
35 : /// },
36 : /// child: Container(),
37 : /// )
38 : /// ```
39 : /// Only specify the [bloc] if you wish to provide a [bloc] that is otherwise
40 : /// not accessible via the current `BuildContext`.
41 : ///
42 : /// ```dart
43 : /// BlocListener<BlocA, BlocAState>(
44 : /// bloc: blocA,
45 : /// listener: (context, state) {
46 : /// // do stuff here based on BlocA's state
47 : /// },
48 : /// child: Container(),
49 : /// )
50 : /// ```
51 : /// {@endtemplate}
52 : ///
53 : /// {@template bloc_listener_listen_when}
54 : /// An optional [listenWhen] can be implemented for more granular control
55 : /// over when [listener] is called.
56 : /// [listenWhen] will be invoked on each [bloc] `state` change.
57 : /// [listenWhen] takes the previous `state` and current `state` and must
58 : /// return a [bool] which determines whether or not the [listener] function
59 : /// will be invoked.
60 : /// The previous `state` will be initialized to the `state` of the [bloc]
61 : /// when the [BlocListener] is initialized.
62 : /// [listenWhen] is optional and if omitted, it will default to `true`.
63 : ///
64 : /// ```dart
65 : /// BlocListener<BlocA, BlocAState>(
66 : /// listenWhen: (previous, current) {
67 : /// // return true/false to determine whether or not
68 : /// // to invoke listener with state
69 : /// },
70 : /// listener: (context, state) {
71 : /// // do stuff here based on BlocA's state
72 : /// }
73 : /// child: Container(),
74 : /// )
75 : /// ```
76 : /// {@endtemplate}
77 : class BlocListener<B extends BlocBase<S>, S extends Object>
78 : extends BlocListenerBase<B, S> implements NestableBlocListener {
79 : /// {@macro bloc_listener}
80 2 : const BlocListener({
81 : Key? key,
82 : B? bloc,
83 : BlocListenerCondition<S>? listenWhen,
84 : required BlocWidgetListener<S> listener,
85 : Widget? child,
86 2 : }) : super(
87 : key: key,
88 : bloc: bloc,
89 : listenWhen: listenWhen,
90 : listener: listener,
91 : child: child,
92 : );
93 :
94 : /// Helps to subscribe to a [bloc]
95 1 : @override
96 1 : void listen() => $use();
97 :
98 1 : @override
99 1 : bool get hasNoChild => child == null;
100 :
101 2 : @override
102 : DiagnosticsNode asDiagnosticsNode() {
103 2 : return DiagnosticsProperty<S>(
104 4 : '$runtimeType',
105 3 : bloc?.state,
106 : ifNull: '',
107 3 : showSeparator: bloc?.state != null,
108 : );
109 : }
110 : }
111 :
112 : /// {@template bloc_listener_base}
113 : /// Base class for widgets that listen to state changes in a specified [bloc].
114 : ///
115 : /// A [BlocListenerBase] is a [BlocWidget] and maintains the state subscription.
116 : /// The type of the state and what happens with each state change
117 : /// is defined by sub-classes.
118 : /// {@endtemplate}
119 : abstract class BlocListenerBase<B extends BlocBase<S>, S extends Object>
120 : extends BlocWidget<B, S> {
121 : /// {@macro bloc_listener_base}
122 3 : const BlocListenerBase({
123 : Key? key,
124 : B? bloc,
125 : this.listenWhen,
126 : required this.listener,
127 : this.child,
128 3 : }) : super(key: key, bloc: bloc);
129 :
130 : /// Takes the previous `state` and the current `state` and is responsible for
131 : /// returning a [bool] which determines whether or not to call [listener]
132 : /// with the current `state`.
133 : final BlocListenerCondition<S>? listenWhen;
134 :
135 : /// Takes the `BuildContext` along with the [bloc] `state`
136 : /// and is responsible for executing in response to `state` changes.
137 : final BlocWidgetListener<S> listener;
138 :
139 : /// The widget which will be rendered as a descendant.
140 : final Widget? child;
141 :
142 1 : @override
143 : Widget build(BuildContext context) {
144 1 : $use();
145 1 : return child!;
146 : }
147 :
148 3 : @override
149 : bool onStateEmitted(BuildContext context, S previous, S state) {
150 5 : if (listenWhen?.call(previous, state) ?? true) {
151 6 : listener.call(context, state);
152 : }
153 : return false;
154 : }
155 :
156 1 : @override
157 : void debugFillProperties(DiagnosticPropertiesBuilder properties) {
158 1 : super.debugFillProperties(properties);
159 2 : properties.add(DiagnosticsProperty<S>(
160 : 'state',
161 2 : bloc?.state,
162 : ifNull: '<null>',
163 2 : showSeparator: bloc?.state != null,
164 : ));
165 : }
166 : }
|