-
-
Notifications
You must be signed in to change notification settings - Fork 3.4k
/
bloc_listener.dart
214 lines (195 loc) · 6.58 KB
/
bloc_listener.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
import 'dart:async';
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:provider/single_child_widget.dart';
/// Mixin which allows `MultiBlocListener` to infer the types
/// of multiple [BlocListener]s.
mixin BlocListenerSingleChildWidget on SingleChildWidget {}
/// Signature for the `listener` function which takes the `BuildContext` along
/// with the `state` and is responsible for executing in response to
/// `state` changes.
typedef BlocWidgetListener<S> = void Function(BuildContext context, S state);
/// Signature for the `listenWhen` function which takes the previous `state`
/// and the current `state` and is responsible for returning a [bool] which
/// determines whether or not to call [BlocWidgetListener] of [BlocListener]
/// with the current `state`.
typedef BlocListenerCondition<S> = bool Function(S previous, S current);
/// {@template bloc_listener}
/// Takes a [BlocWidgetListener] and an optional [bloc] and invokes
/// the [listener] in response to `state` changes in the [bloc].
/// It should be used for functionality that needs to occur only in response to
/// a `state` change such as navigation, showing a `SnackBar`, showing
/// a `Dialog`, etc...
/// The [listener] is guaranteed to only be called once for each `state` change
/// unlike the `builder` in `BlocBuilder`.
///
/// If the [bloc] parameter is omitted, [BlocListener] will automatically
/// perform a lookup using [BlocProvider] and the current `BuildContext`.
///
/// ```dart
/// BlocListener<BlocA, BlocAState>(
/// listener: (context, state) {
/// // do stuff here based on BlocA's state
/// },
/// child: Container(),
/// )
/// ```
/// Only specify the [bloc] if you wish to provide a [bloc] that is otherwise
/// not accessible via [BlocProvider] and the current `BuildContext`.
///
/// ```dart
/// BlocListener<BlocA, BlocAState>(
/// value: blocA,
/// listener: (context, state) {
/// // do stuff here based on BlocA's state
/// },
/// child: Container(),
/// )
/// ```
/// {@endtemplate}
///
/// {@template bloc_listener_listen_when}
/// An optional [listenWhen] can be implemented for more granular control
/// over when [listener] is called.
/// [listenWhen] will be invoked on each [bloc] `state` change.
/// [listenWhen] takes the previous `state` and current `state` and must
/// return a [bool] which determines whether or not the [listener] function
/// will be invoked.
/// The previous `state` will be initialized to the `state` of the [bloc]
/// when the [BlocListener] is initialized.
/// [listenWhen] is optional and if omitted, it will default to `true`.
///
/// ```dart
/// BlocListener<BlocA, BlocAState>(
/// listenWhen: (previous, current) {
/// // return true/false to determine whether or not
/// // to invoke listener with state
/// },
/// listener: (context, state) {
/// // do stuff here based on BlocA's state
/// }
/// child: Container(),
/// )
/// ```
/// {@endtemplate}
class BlocListener<B extends StateStreamable<S>, S>
extends BlocListenerBase<B, S> with BlocListenerSingleChildWidget {
/// {@macro bloc_listener}
/// {@macro bloc_listener_listen_when}
const BlocListener({
Key? key,
required BlocWidgetListener<S> listener,
B? bloc,
BlocListenerCondition<S>? listenWhen,
Widget? child,
}) : super(
key: key,
child: child,
listener: listener,
bloc: bloc,
listenWhen: listenWhen,
);
}
/// {@template bloc_listener_base}
/// Base class for widgets that listen to state changes in a specified [bloc].
///
/// A [BlocListenerBase] is stateful and maintains the state subscription.
/// The type of the state and what happens with each state change
/// is defined by sub-classes.
/// {@endtemplate}
abstract class BlocListenerBase<B extends StateStreamable<S>, S>
extends SingleChildStatefulWidget {
/// {@macro bloc_listener_base}
const BlocListenerBase({
Key? key,
required this.listener,
this.bloc,
this.child,
this.listenWhen,
}) : super(key: key, child: child);
/// The widget which will be rendered as a descendant of the
/// [BlocListenerBase].
final Widget? child;
/// The [bloc] whose `state` will be listened to.
/// Whenever the [bloc]'s `state` changes, [listener] will be invoked.
final B? bloc;
/// The [BlocWidgetListener] which will be called on every `state` change.
/// This [listener] should be used for any code which needs to execute
/// in response to a `state` change.
final BlocWidgetListener<S> listener;
/// {@macro bloc_listener_listen_when}
final BlocListenerCondition<S>? listenWhen;
@override
SingleChildState<BlocListenerBase<B, S>> createState() =>
_BlocListenerBaseState<B, S>();
}
class _BlocListenerBaseState<B extends StateStreamable<S>, S>
extends SingleChildState<BlocListenerBase<B, S>> {
StreamSubscription<S>? _subscription;
late B _bloc;
late S _previousState;
@override
void initState() {
super.initState();
_bloc = widget.bloc ?? context.read<B>();
_previousState = _bloc.state;
_subscribe();
}
@override
void didUpdateWidget(BlocListenerBase<B, S> oldWidget) {
super.didUpdateWidget(oldWidget);
final oldBloc = oldWidget.bloc ?? context.read<B>();
final currentBloc = widget.bloc ?? oldBloc;
if (oldBloc != currentBloc) {
if (_subscription != null) {
_unsubscribe();
_bloc = currentBloc;
_previousState = _bloc.state;
}
_subscribe();
}
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
final bloc = widget.bloc ?? context.read<B>();
if (_bloc != bloc) {
if (_subscription != null) {
_unsubscribe();
_bloc = bloc;
_previousState = _bloc.state;
}
_subscribe();
}
}
@override
Widget buildWithChild(BuildContext context, Widget? child) {
assert(
child != null,
'''${widget.runtimeType} used outside of MultiBlocListener must specify a child''',
);
if (widget.bloc == null) {
// Trigger a rebuild if the bloc reference has changed.
// See https://github.com/felangel/bloc/issues/2127.
context.select<B, bool>((bloc) => identical(_bloc, bloc));
}
return child!;
}
@override
void dispose() {
_unsubscribe();
super.dispose();
}
void _subscribe() {
_subscription = _bloc.stream.listen((state) {
if (widget.listenWhen?.call(_previousState, state) ?? true) {
widget.listener(context, state);
}
_previousState = state;
});
}
void _unsubscribe() {
_subscription?.cancel();
_subscription = null;
}
}