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