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