felangel / bloc

A predictable state management library that helps implement the BLoC design pattern
https://bloclibrary.dev
MIT License
11.8k stars 3.39k forks source link

Bad state: Cannot add new events after calling close after the bloc is disposed. #120

Closed haqqi closed 5 years ago

haqqi commented 5 years ago

I got this error while trying a scenario:

  1. I need to fetch some data from the API.
  2. To simulate the fetching delay time, i use await Future.delayed(Duration(seconds: 2)); in the mapEventToState bloc. Then i yield some state.
  3. I dispose the bloc when the page is popped.
  4. When i press back (dispose) the page before the fetching finished (under 2 seconds), the error below happened. The apps did not crashed though.

Seems like it is similar with #52

E/flutter (14028): [ERROR:flutter/lib/ui/ui_dart_state.cc(148)] Unhandled Exception: Bad state: Cannot add new events after calling close
E/flutter (14028): #0      _BroadcastStreamController.add (dart:async/broadcast_stream_controller.dart:249:24)
E/flutter (14028): #1      Subject._add (package:rxdart/src/subjects/subject.dart:124:16)
E/flutter (14028): #2      Subject.add (package:rxdart/src/subjects/subject.dart:118:5)
E/flutter (14028): #3      Bloc._bindStateSubject.<anonymous closure> (package:bloc/src/bloc.dart:86:23)
E/flutter (14028): #4      Stream.forEach.<anonymous closure>.<anonymous closure> (dart:async/stream.dart:814:45)
E/flutter (14028): #5      _runUserCode (dart:async/stream_pipe.dart:11:23)
E/flutter (14028): #6      Stream.forEach.<anonymous closure> (dart:async/stream.dart:814:11)
E/flutter (14028): #7      _rootRunUnary (dart:async/zone.dart:1132:38)
E/flutter (14028): #8      _CustomZone.runUnary (dart:async/zone.dart:1029:19)
E/flutter (14028): #9      _CustomZone.runUnaryGuarded (dart:async/zone.dart:931:7)
E/flutter (14028): #10     _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:336:11)
E/flutter (14028): #11     _BufferingStreamSubscription._add (dart:async/stream_impl.dart:263:7)
E/flutter (14028): #12     _SyncBroadcastStreamController._sendData (dart:async/broadcast_stream_controller.dart:375:20)
E/flutter (14028): #13     _BroadcastStreamController._add (dart:async/broadcast_stream_controller.dart:287:5)
E/flutter (14028): #14     _rootRunUnary (dart:async/zone.dart:1132:38)
E/flutter (14028): #15     _CustomZone.runUnary (dart:async/zone.dart:1029:19)
E/flutter (14028): #16     _CustomZone.runUnaryGuarded (dart:async/zone.dart:931:7)
E/flutter (14028): #17     _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:336:11)
E/flutter (14028): #18     _BufferingStreamSubscription._add (dart:async/stream_impl.dart:263:7)
E/flutter (14028): #19     _ForwardingStreamSubscription._add (dart:async/stream_pipe.dart:132:11)
E/flutter (14028): #20     _ForwardingStream._handleData (dart:async/stream_pipe.dart:98:10)
E/flutter (14028): #21     _ForwardingStreamSubscription._handleData (dart:async/stream_pipe.dart:164:13)
E/flutter (14028): #22     _rootRunUnary (dart:async/zone.dart:1132:38)
E/flutter (14028): #23     _CustomZone.runUnary (dart:async/zone.dart:1029:19)
E/flutter (14028): #24     _CustomZone.runUnaryGuarded (dart:async/zone.dart:931:7)
E/flutter (14028): #25     _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:336:11)
E/flutter (14028): #26     _DelayedData.perform (dart:async/stream_impl.dart:591:14)
E/flutter (14028): #27     _StreamImplEvents.handleNext (dart:async/stream_impl.dart:707:11)
E/flutter (14028): #28     _PendingEvents.schedule.<anonymous closure> (dart:async/stream_impl.dart:667:7)
E/flutter (14028): #29     _rootRun (dart:async/zone.dart:1120:38)
E/flutter (14028): #30     _CustomZone.run (dart:async/zone.dart:1021:19)
E/flutter (14028): #31     _CustomZone.runGuarded (dart:async/zone.dart:923:7)
E/flutter (14028): #32     _CustomZone.bindCallbackGuarded.<anonymous closure> (dart:async/zone.dart:963:23)
E/flutter (14028): #33     _rootRun (dart:async/zone.dart:1124:13)
E/flutter (14028): #34     _CustomZone.run (dart:async/zone.dart:1021:19)
E/flutter (14028): #35     _CustomZone.runGuarded (dart:async/zone.dart:923:7)
E/flutter (14028): #36     _CustomZone.bindCallbackGuarded.<anonymous closure> (dart:async/zone.dart:963:23)
E/flutter (14028): #37     _microtaskLoop (dart:async/schedule_microtask.dart:41:21)
E/flutter (14028): #38     _startMicrotaskLoop (dart:async/schedule_microtask.dart:50:5)
E/flutter (14028): 
hadukin commented 1 year ago

@7Kronos hi, a possible option is to use extension like this

extension BlocBaseExtension<T> on BlocBase<T> {
  void maybeEmit(T state) {
    if (!isClosed) emit(state);
  }
}
nogicoder commented 1 year ago

@7Kronos hi, a possible option is to use extension like this

extension BlocBaseExtension<T> on BlocBase<T> {
  void maybeEmit(T state) {
    if (!isClosed) emit(state);
  }
}

This produces a warning since emit is protected

rohit-suthar commented 7 months ago

@felangel What will be the appropriate fix for this case? https://github.com/felangel/bloc/issues/120#issuecomment-1708567614

silkstream-tristan commented 7 months ago

@felangel What will be the appropriate fix for this case? #120 (comment)

Below is the snippet of code in version 7 and then version 8 of the package.

Flutter Bloc Version 7.3.3

https://github.com/felangel/bloc/blob/32a12785aadc4d9b0295982d7e19b9d873cc49db/packages/bloc/lib/src/bloc.dart#L289-L301

Flutter Bloc Version 8.1.4

https://github.com/felangel/bloc/blob/0ae053709d8ec3f563d6b8bcb6ac96e5579f5d5b/packages/bloc/lib/src/bloc.dart#L76-L105

You can see that the check for _eventController.isClosed was removed from version 8.

My proposal would be to check that the bloc state controller is not closed as this is what my workaround uses. This is different to the event controller that was checked in version 7. There must have been a reason for the changes between 7 and 8 so I'm not sure if there are any other implications of this proposal.

  @override
  void add(Event event) {
    // ignore: prefer_asserts_with_message
    assert(() {
      final handlerExists = _handlers.any((handler) => handler.isType(event));
      if (!handlerExists) {
        final eventType = event.runtimeType;
        throw StateError(
          '''add($eventType) was called without a registered event handler.\n'''
          '''Make sure to register a handler via on<$eventType>((event, emit) {...})''',
        );
      }
      return true;
    }());
    if (isClosed) return; // <--- This is the added line.
    try {
      onEvent(event);
      _eventController.add(event);
    } catch (error, stackTrace) {
      onError(error, stackTrace);
      rethrow;
    }
  }
zamaniafshar commented 5 months ago

One of the easiest and best approaches is to ignore such errors but print them to the console, which can be achieved with the code below:


void main() {
  _handleErrors();
  //  runApp ...
}

// Bloc.emit state errors are ignored but they are still logged in the console.
void _handleErrors() {
  FlutterError.onError = (details) {
    final exception = details.exception;
    if (_isBlocStateError(exception)) {
      print('Bloc emit state error: $details');
      return;
    }

    FlutterError.presentError(details);
  };

  PlatformDispatcher.instance.onError = (exception, stackTrace) {
    if (_isBlocStateError(exception)) {
      print('Bloc emit state error: $exception');
      return true;
    }

    return false;
  };
}

bool _isBlocStateError(Object exception) {
  final isStateError = exception is StateError;
  if (!isStateError) return false;
  return exception.message == 'Cannot emit new states after calling close';
}