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