Line data Source code
1 : import 'dart:async';
2 :
3 : import 'package:bloc/bloc.dart';
4 : import 'package:meta/meta.dart';
5 :
6 : /// {@template emitter}
7 : /// An [Emitter] is a class which is capable of emitting new states.
8 : ///
9 : /// See also:
10 : ///
11 : /// * [EventHandler] which has access to an [Emitter].
12 : ///
13 : /// {@endtemplate}
14 : abstract class Emitter<State> {
15 : /// Subscribes to the provided [stream] and invokes the [onData] callback
16 : /// when the [stream] emits new data.
17 : ///
18 : /// [onEach] completes when the event handler is cancelled or when
19 : /// the provided [stream] has ended.
20 : ///
21 : /// If [onError] is omitted, any errors on this [stream]
22 : /// are considered unhandled, and will be thrown by [onEach].
23 : /// As a result, the internal subscription to the [stream] will be canceled.
24 : ///
25 : /// If [onError] is provided, any errors on this [stream] will be passed on to
26 : /// [onError] and will not result in unhandled exceptions or cancelations to
27 : /// the internal stream subscription.
28 : ///
29 : /// **Note**: The stack trace argument may be [StackTrace.empty]
30 : /// if the [stream] received an error without a stack trace.
31 : Future<void> onEach<T>(
32 : Stream<T> stream, {
33 : required void Function(T data) onData,
34 : void Function(Object error, StackTrace stackTrace)? onError,
35 : });
36 :
37 : // Subscribes to the provided [stream] and invokes the [onData] callback
38 : /// when the [stream] emits new data and the result of [onData] is emitted.
39 : ///
40 : /// [forEach] completes when the event handler is cancelled or when
41 : /// the provided [stream] has ended.
42 : ///
43 : /// If [onError] is omitted, any errors on this [stream]
44 : /// are considered unhandled, and will be thrown by [forEach].
45 : /// As a result, the internal subscription to the [stream] will be canceled.
46 : ///
47 : /// If [onError] is provided, any errors on this [stream] will be passed on to
48 : /// [onError] and will not result in unhandled exceptions or cancelations to
49 : /// the internal stream subscription.
50 : ///
51 : /// **Note**: The stack trace argument may be [StackTrace.empty]
52 : /// if the [stream] received an error without a stack trace.
53 : Future<void> forEach<T>(
54 : Stream<T> stream, {
55 : required State Function(T data) onData,
56 : State Function(Object error, StackTrace stackTrace)? onError,
57 : });
58 :
59 : /// Whether the [EventHandler] associated with this [Emitter]
60 : /// has been completed or canceled.
61 : bool get isDone;
62 :
63 : /// Emits the provided [state].
64 : void call(State state);
65 : }
66 :
67 : /// An event handler is responsible for reacting to an incoming [Event]
68 : /// and can emit zero or more states via the [Emitter].
69 : typedef EventHandler<Event, State> = FutureOr<void> Function(
70 : Event event,
71 : Emitter<State> emit,
72 : );
73 :
74 : /// Signature for a function which converts an incoming event
75 : /// into an outbound stream of events.
76 : /// Used when defining custom [EventTransformer]s.
77 : typedef EventMapper<Event> = Stream<Event> Function(Event event);
78 :
79 : /// Used to change how events are processed.
80 : /// By default events are processed concurrently.
81 : typedef EventTransformer<Event> = Stream<Event> Function(
82 : Stream<Event> events,
83 : EventMapper<Event> mapper,
84 : );
85 :
86 : class _BlocEmitter<State> extends _BaseEmitter<State>
87 : implements Emitter<State> {
88 2 : _BlocEmitter(void Function(State) emit) : super(emit);
89 :
90 1 : @override
91 : void call(State state) {
92 : assert(
93 2 : !_isCompleted,
94 : '''\n\n
95 : emit was called after an event handler completed normally.
96 : This is usually due to an unawaited future in an event handler.
97 : Please make sure to await all asynchronous operations with event handlers
98 : and use emit.isDone after asynchronous operations before calling emit() to
99 : ensure the event handler has not completed.
100 :
101 : **BAD**
102 : on<Event>((event, emit) {
103 : future.whenComplete(() => emit(...));
104 : });
105 :
106 : **GOOD**
107 : on<Event>((event, emit) async {
108 : await future.whenComplete(() => emit(...));
109 : });
110 : ''',
111 : );
112 1 : super.call(state);
113 : }
114 :
115 1 : @override
116 : void complete() {
117 : assert(
118 2 : _disposables.isEmpty,
119 : '''\n\n
120 : An event handler completed but left pending subscriptions behind.
121 : This is most likely due to an unawaited emit.forEach or emit.onEach.
122 : Please make sure to await all asynchronous operations within event handlers.
123 :
124 : **BAD**
125 : on<Event>((event, emit) {
126 : emit.forEach(...);
127 : });
128 :
129 : **GOOD**
130 : on<Event>((event, emit) async {
131 : await emit.forEach(...);
132 : });
133 :
134 : **GOOD**
135 : on<Event>((event, emit) {
136 : return emit.forEach(...);
137 : });
138 :
139 : **GOOD**
140 : on<Event>((event, emit) => emit.forEach(...));
141 :
142 : ''',
143 : );
144 1 : super.complete();
145 : }
146 : }
147 :
148 : class _BaseEmitter<State> implements Emitter<State> {
149 1 : _BaseEmitter(this._emit);
150 :
151 : final void Function(State) _emit;
152 : final _completer = Completer<void>();
153 : final _disposables = <FutureOr<void> Function()>[];
154 :
155 : var _isCanceled = false;
156 : var _isCompleted = false;
157 :
158 : @override
159 1 : Future<void> onEach<T>(
160 : Stream<T> stream, {
161 : required void Function(T) onData,
162 : void Function(Object error, StackTrace stackTrace)? onError,
163 : }) async {
164 1 : final completer = Completer<void>();
165 1 : final subscription = stream.listen(
166 : onData,
167 1 : onDone: completer.complete,
168 1 : onError: onError ?? completer.completeError,
169 : cancelOnError: onError == null,
170 : );
171 3 : _disposables.add(subscription.cancel);
172 6 : return Future.any([future, completer.future]).whenComplete(() {
173 1 : subscription.cancel();
174 3 : _disposables.remove(subscription.cancel);
175 : });
176 : }
177 :
178 1 : @override
179 : Future<void> forEach<T>(
180 : Stream<T> stream, {
181 : required State Function(T) onData,
182 : State Function(Object error, StackTrace stackTrace)? onError,
183 : }) {
184 1 : return onEach<T>(
185 : stream,
186 2 : onData: (data) => call(onData(data)),
187 : onError: onError != null
188 1 : ? (Object error, StackTrace stackTrace) {
189 1 : call(onError(error, stackTrace));
190 : }
191 : : null,
192 : );
193 : }
194 :
195 1 : @override
196 : void call(State state) {
197 2 : if (!_isCanceled) _emit(state);
198 : }
199 :
200 1 : @override
201 2 : bool get isDone => _isCanceled || _isCompleted;
202 :
203 1 : void cancel() {
204 1 : if (isDone) return;
205 1 : _isCanceled = true;
206 1 : _close();
207 : }
208 :
209 1 : void complete() {
210 1 : if (isDone) return;
211 1 : _isCompleted = true;
212 1 : _close();
213 : }
214 :
215 1 : void _close() {
216 2 : for (final disposable in _disposables) disposable.call();
217 2 : _disposables.clear();
218 4 : if (!_completer.isCompleted) _completer.complete();
219 : }
220 :
221 3 : Future<void> get future => _completer.future;
222 : }
223 :
224 : /// {@template bloc_unhandled_error_exception}
225 : /// Exception thrown when an unhandled error occurs within a bloc.
226 : ///
227 : /// _Note: thrown in debug mode only_
228 : /// {@endtemplate}
229 : class BlocUnhandledErrorException implements Exception {
230 : /// {@macro bloc_unhandled_error_exception}
231 1 : BlocUnhandledErrorException(
232 : this.bloc,
233 : this.error, [
234 : this.stackTrace = StackTrace.empty,
235 : ]);
236 :
237 : /// The bloc in which the unhandled error occurred.
238 : final BlocBase bloc;
239 :
240 : /// The unhandled [error] object.
241 : final Object error;
242 :
243 : /// Stack trace which accompanied the error.
244 : /// May be [StackTrace.empty] if no stack trace was provided.
245 : final StackTrace stackTrace;
246 :
247 1 : @override
248 : String toString() {
249 3 : return 'Unhandled error $error occurred in $bloc.\n'
250 1 : '$stackTrace';
251 : }
252 : }
253 :
254 : class _Handler {
255 1 : const _Handler({required this.isType, required this.type});
256 : final bool Function(dynamic) isType;
257 : final Type type;
258 : }
259 :
260 : /// {@template bloc}
261 : /// Takes a `Stream` of `Events` as input
262 : /// and transforms them into a `Stream` of `States` as output.
263 : /// {@endtemplate}
264 : abstract class Bloc<Event, State> extends BlocBase<State> {
265 : /// {@macro bloc}
266 2 : Bloc(State initialState) : super(initialState);
267 :
268 : /// The current [BlocObserver] instance.
269 3 : static BlocObserver observer = BlocObserver();
270 :
271 : /// The default [EventTransformer] used for all event handlers.
272 : /// By default all events are processed concurrently.
273 : ///
274 : /// If a custom transformer is specified for a particular event handler,
275 : /// it will take precendence over the global transformer.
276 : ///
277 : /// See also:
278 : ///
279 : /// * [package:bloc_concurrency](https://pub.dev/packages/bloc_concurrency) for an
280 : /// opinionated set of event transformers.
281 : ///
282 3 : static EventTransformer<dynamic> transformer = (events, mapper) {
283 : return events
284 1 : .map(mapper)
285 1 : .transform<dynamic>(const _FlatMapStreamTransformer<dynamic>());
286 : };
287 :
288 : final _eventController = StreamController<Event>.broadcast();
289 : final _subscriptions = <StreamSubscription<dynamic>>[];
290 : final _handlers = <_Handler>[];
291 : final _emitters = <_BlocEmitter>[];
292 :
293 : /// Notifies the [Bloc] of a new [event] which triggers
294 : /// all corresponding [EventHandler] instances.
295 : /// If [close] has already been called, any subsequent calls to [add] will
296 : /// be ignored and will not result in any subsequent state changes.
297 : ///
298 : /// * A [StateError] will be thrown if there is no event handler
299 : /// registered for the incoming [event].
300 1 : void add(Event event) {
301 1 : assert(() {
302 4 : final handlerExists = _handlers.any((handler) => handler.isType(event));
303 : if (!handlerExists) {
304 1 : final eventType = event.runtimeType;
305 2 : throw StateError(
306 : '''add($eventType) was called without a registered event handler.\n'''
307 : '''Make sure to register a handler via on<$eventType>((event, emit) {...})''',
308 : );
309 : }
310 : return true;
311 : }());
312 2 : if (_eventController.isClosed) return;
313 : try {
314 1 : onEvent(event);
315 2 : _eventController.add(event);
316 : } catch (error, stackTrace) {
317 1 : onError(error, stackTrace);
318 : }
319 : }
320 :
321 : /// Called whenever an [event] is [add]ed to the [Bloc].
322 : /// A great spot to add logging/analytics at the individual [Bloc] level.
323 : ///
324 : /// **Note: `super.onEvent` should always be called first.**
325 : /// ```dart
326 : /// @override
327 : /// void onEvent(Event event) {
328 : /// // Always call super.onEvent with the current event
329 : /// super.onEvent(event);
330 : ///
331 : /// // Custom onEvent logic goes here
332 : /// }
333 : /// ```
334 : ///
335 : /// See also:
336 : ///
337 : /// * [BlocObserver.onEvent] for observing events globally.
338 : ///
339 1 : @protected
340 : @mustCallSuper
341 : void onEvent(Event event) {
342 : // ignore: invalid_use_of_protected_member
343 2 : observer.onEvent(this, event);
344 : }
345 :
346 : /// {@template emit}
347 : /// **[emit] is only for internal use and should never be called directly.
348 : /// The [Emitter] instance provided to each [EventHandler]
349 : /// should be used instead.**
350 : ///
351 : /// ```dart
352 : /// class MyBloc extends Bloc<MyEvent, MyState> {
353 : /// MyBloc() : super(MyInitialState()) {
354 : /// on<MyEvent>((event, emit) {
355 : /// // use `emit` to update the state.
356 : /// emit(MyOtherState());
357 : /// });
358 : /// }
359 : /// }
360 : /// ```
361 : ///
362 : /// Updates the state of the bloc to the provided [state].
363 : /// A bloc's state should only be updated by `emitting` a new `state`
364 : /// from an [EventHandler] in response to an incoming event.
365 : /// {@endtemplate}
366 1 : @internal
367 : @override
368 2 : Emitter<State> get emit => _BlocEmitter(_emit);
369 :
370 : /// Register event handler for an event of type `E`.
371 : /// There should only ever be one event handler per event type `E`.
372 : ///
373 : /// ```dart
374 : /// abstract class CounterEvent {}
375 : /// class Increment extends CounterEvent {}
376 : ///
377 : /// class CounterBloc extends Bloc<CounterEvent, int> {
378 : /// CounterBloc() : super(0) {
379 : /// on<Increment>((event, emit) => emit(state + 1));
380 : /// }
381 : /// }
382 : /// ```
383 : ///
384 : /// * A [StateError] will be thrown if there are multiple event handlers
385 : /// registered for the same type `E`.
386 : ///
387 : /// By default, events will be processed concurrently.
388 : ///
389 : /// See also:
390 : ///
391 : /// * [EventTransformer] to customize how events are processed.
392 : /// * [package:bloc_concurrency](https://pub.dev/packages/bloc_concurrency) for an
393 : /// opinionated set of event transformers.
394 : ///
395 1 : void on<E extends Event>(
396 : EventHandler<E, State> handler, {
397 : EventTransformer<E>? transformer,
398 : }) {
399 1 : assert(() {
400 5 : final handlerExists = _handlers.any((handler) => handler.type == E);
401 : if (handlerExists) {
402 2 : throw StateError(
403 : 'on<$E> was called multiple times. '
404 : 'There should only be a single event handler per event type.',
405 : );
406 : }
407 5 : _handlers.add(_Handler(isType: (dynamic e) => e is E, type: E));
408 : return true;
409 : }());
410 :
411 1 : final _transformer = transformer ?? Bloc.transformer;
412 : final subscription = _transformer(
413 6 : _eventController.stream.where((event) => event is E).cast<E>(),
414 1 : (dynamic event) {
415 1 : void onEmit(State state) {
416 1 : if (isClosed) return;
417 3 : if (this.state == state && _emitted) return;
418 2 : onTransition(Transition(
419 1 : currentState: this.state,
420 : event: event as E,
421 : nextState: state,
422 : ));
423 2 : emit(state);
424 : }
425 :
426 1 : final emitter = _BlocEmitter(onEmit);
427 1 : final controller = StreamController<E>.broadcast(
428 : sync: true,
429 1 : onCancel: emitter.cancel,
430 : );
431 :
432 1 : void handleEvent() async {
433 1 : void onDone() {
434 1 : emitter.complete();
435 2 : _emitters.remove(emitter);
436 2 : if (!controller.isClosed) controller.close();
437 : }
438 :
439 : try {
440 2 : _emitters.add(emitter);
441 1 : await handler(event as E, emitter);
442 : } catch (error, stackTrace) {
443 1 : onError(error, stackTrace);
444 : } finally {
445 : onDone();
446 : }
447 : }
448 :
449 : handleEvent();
450 1 : return controller.stream;
451 : },
452 1 : ).listen(null);
453 2 : _subscriptions.add(subscription);
454 : }
455 :
456 : /// Called whenever a [transition] occurs with the given [transition].
457 : /// A [transition] occurs when a new `event` is added
458 : /// and a new state is `emitted` from a corresponding [EventHandler].
459 : /// executed.
460 : /// [onTransition] is called before a [Bloc]'s [state] has been updated.
461 : /// A great spot to add logging/analytics at the individual [Bloc] level.
462 : ///
463 : /// **Note: `super.onTransition` should always be called first.**
464 : /// ```dart
465 : /// @override
466 : /// void onTransition(Transition<Event, State> transition) {
467 : /// // Always call super.onTransition with the current transition
468 : /// super.onTransition(transition);
469 : ///
470 : /// // Custom onTransition logic goes here
471 : /// }
472 : /// ```
473 : ///
474 : /// See also:
475 : ///
476 : /// * [BlocObserver.onTransition] for observing transitions globally.
477 : ///
478 1 : @protected
479 : @mustCallSuper
480 : void onTransition(Transition<Event, State> transition) {
481 : // ignore: invalid_use_of_protected_member
482 2 : Bloc.observer.onTransition(this, transition);
483 : }
484 :
485 : /// Closes the `event` and `state` `Streams`.
486 : /// This method should be called when a [Bloc] is no longer needed.
487 : /// Once [close] is called, `events` that are [add]ed will not be
488 : /// processed.
489 : /// In addition, if [close] is called while `events` are still being
490 : /// processed, the [Bloc] will finish processing the pending `events`.
491 : @override
492 : @mustCallSuper
493 1 : Future<void> close() async {
494 3 : await _eventController.close();
495 3 : for (final emitter in _emitters) emitter.cancel();
496 6 : await Future.wait<void>(_emitters.map((e) => e.future));
497 6 : await Future.wait<void>(_subscriptions.map((s) => s.cancel()));
498 1 : return super.close();
499 : }
500 : }
501 :
502 : /// {@template cubit}
503 : /// A [Cubit] is similar to [Bloc] but has no notion of events
504 : /// and relies on methods to [emit] new states.
505 : ///
506 : /// Every [Cubit] requires an initial state which will be the
507 : /// state of the [Cubit] before [emit] has been called.
508 : ///
509 : /// The current state of a [Cubit] can be accessed via the [state] getter.
510 : ///
511 : /// ```dart
512 : /// class CounterCubit extends Cubit<int> {
513 : /// CounterCubit() : super(0);
514 : ///
515 : /// void increment() => emit(state + 1);
516 : /// }
517 : /// ```
518 : ///
519 : /// {@endtemplate}
520 : abstract class Cubit<State> extends BlocBase<State> {
521 : /// {@macro cubit}
522 2 : Cubit(State initialState) : super(initialState);
523 :
524 : final _emitters = <_BaseEmitter>[];
525 :
526 1 : @override
527 : Emitter<State> get emit {
528 : late _BaseEmitter<State> emitter;
529 2 : emitter = _BaseEmitter<State>((state) {
530 1 : _emit(state);
531 2 : _emitters.remove(emitter);
532 : });
533 2 : _emitters.add(emitter);
534 2 : emitter.future.whenComplete(() => _emitters.remove(emitter));
535 0 : return emitter; // coverage:ignore-line
536 : }
537 :
538 : @override
539 1 : Future<void> close() async {
540 3 : print(_emitters.length);
541 1 : for (final emitter in _emitters) emitter.cancel();
542 4 : await Future.wait<void>(_emitters.map((e) => e.future));
543 1 : return super.close();
544 : }
545 : }
546 :
547 : /// {@template bloc_stream}
548 : /// An interface for the core functionality implemented by
549 : /// both [Bloc] and [Cubit].
550 : /// {@endtemplate}
551 : abstract class BlocBase<State> {
552 : /// {@macro bloc_stream}
553 1 : BlocBase(this._state) {
554 : // ignore: invalid_use_of_protected_member
555 2 : Bloc.observer.onCreate(this);
556 : }
557 :
558 : StreamController<State>? __stateController;
559 1 : StreamController<State> get _stateController {
560 2 : return __stateController ??= StreamController<State>.broadcast();
561 : }
562 :
563 : State _state;
564 :
565 : bool _emitted = false;
566 :
567 : /// The current [state].
568 2 : State get state => _state;
569 :
570 : /// The current state stream.
571 3 : Stream<State> get stream => _stateController.stream;
572 :
573 : /// Whether the bloc is closed.
574 : ///
575 : /// A bloc is considered closed once [close] is called.
576 : /// Subsequent state changes cannot occur within a closed bloc.
577 3 : bool get isClosed => _stateController.isClosed;
578 :
579 : /// An [Emitter] instance which can be used to trigger state changes.
580 : /// [emit] updates the [state] to the provided [state].
581 : /// [emit] does nothing if the instance has been closed or if the
582 : /// [state] being emitted is equal to the current [state].
583 : ///
584 : /// To allow for the possibility of notifying listeners of the initial state,
585 : /// emitting a state which is equal to the initial state is allowed as long
586 : /// as it is the first thing emitted by the instance.
587 : @protected
588 : Emitter<State> get emit;
589 :
590 1 : void _emit(State state) {
591 2 : if (_stateController.isClosed) return;
592 3 : if (state == _state && _emitted) return;
593 3 : onChange(Change<State>(currentState: this.state, nextState: state));
594 1 : _state = state;
595 3 : _stateController.add(_state);
596 1 : _emitted = true;
597 : }
598 :
599 : /// Called whenever a [change] occurs with the given [change].
600 : /// A [change] occurs when a new `state` is emitted.
601 : /// [onChange] is called before the `state` of the `cubit` is updated.
602 : /// [onChange] is a great spot to add logging/analytics for a specific `cubit`.
603 : ///
604 : /// **Note: `super.onChange` should always be called first.**
605 : /// ```dart
606 : /// @override
607 : /// void onChange(Change change) {
608 : /// // Always call super.onChange with the current change
609 : /// super.onChange(change);
610 : ///
611 : /// // Custom onChange logic goes here
612 : /// }
613 : /// ```
614 : ///
615 : /// See also:
616 : ///
617 : /// * [BlocObserver] for observing [Cubit] behavior globally.
618 : ///
619 1 : @mustCallSuper
620 : void onChange(Change<State> change) {
621 : // ignore: invalid_use_of_protected_member
622 2 : Bloc.observer.onChange(this, change);
623 : }
624 :
625 : /// Reports an [error] which triggers [onError] with an optional [StackTrace].
626 1 : @mustCallSuper
627 : void addError(Object error, [StackTrace? stackTrace]) {
628 1 : onError(error, stackTrace ?? StackTrace.current);
629 : }
630 :
631 : /// Called whenever an [error] occurs and notifies [BlocObserver.onError].
632 : ///
633 : /// In debug mode, [onError] throws a [BlocUnhandledErrorException] for
634 : /// improved visibility.
635 : ///
636 : /// In release mode, [onError] does not throw and will instead only report
637 : /// the error to [BlocObserver.onError].
638 : ///
639 : /// **Note: `super.onError` should always be called last.**
640 : /// ```dart
641 : /// @override
642 : /// void onError(Object error, StackTrace stackTrace) {
643 : /// // Custom onError logic goes here
644 : ///
645 : /// // Always call super.onError with the current error and stackTrace
646 : /// super.onError(error, stackTrace);
647 : /// }
648 : /// ```
649 1 : @protected
650 : @mustCallSuper
651 : void onError(Object error, StackTrace stackTrace) {
652 : // ignore: invalid_use_of_protected_member
653 2 : Bloc.observer.onError(this, error, stackTrace);
654 1 : assert(() {
655 1 : throw BlocUnhandledErrorException(this, error, stackTrace);
656 : }());
657 : }
658 :
659 : /// Closes the instance.
660 : /// This method should be called when the instance is no longer needed.
661 : /// Once [close] is called, the instance can no longer be used.
662 : @mustCallSuper
663 1 : Future<void> close() async {
664 : // ignore: invalid_use_of_protected_member
665 2 : Bloc.observer.onClose(this);
666 3 : await _stateController.close();
667 : }
668 : }
669 :
670 : class _FlatMapStreamTransformer<T> extends StreamTransformerBase<Stream<T>, T> {
671 1 : const _FlatMapStreamTransformer();
672 :
673 1 : @override
674 : Stream<T> bind(Stream<Stream<T>> stream) {
675 1 : final controller = StreamController<T>.broadcast(sync: true);
676 :
677 2 : controller.onListen = () {
678 1 : final subscriptions = <StreamSubscription<dynamic>>[];
679 :
680 1 : final outerSubscription = stream.listen(
681 1 : (inner) {
682 1 : final subscription = inner.listen(
683 1 : controller.add,
684 1 : onError: controller.addError,
685 : );
686 :
687 2 : subscription.onDone(() {
688 1 : subscriptions.remove(subscription);
689 1 : if (subscriptions.isEmpty) controller.close();
690 : });
691 :
692 1 : subscriptions.add(subscription);
693 : },
694 1 : onError: controller.addError,
695 : );
696 :
697 2 : outerSubscription.onDone(() {
698 1 : subscriptions.remove(outerSubscription);
699 2 : if (subscriptions.isEmpty) controller.close();
700 : });
701 :
702 1 : subscriptions.add(outerSubscription);
703 :
704 2 : controller.onCancel = () {
705 1 : if (subscriptions.isEmpty) return null;
706 3 : final cancels = [for (final s in subscriptions) s.cancel()];
707 3 : return Future.wait(cancels).then((_) {});
708 : };
709 : };
710 :
711 1 : return controller.stream;
712 : }
713 : }
|