LCOV - code coverage report
Current view: top level - src - bloc.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 170 170 100.0 %
Date: 2021-09-09 20:44:29 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             :   ///
      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             : }

Generated by: LCOV version 1.15