LCOV - code coverage report
Current view: top level - src - bloc.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 168 169 99.4 %
Date: 2021-09-09 14:16:36 Functions: 0 0 -

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

Generated by: LCOV version 1.15