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