Bloc is either calling multiple times or not getting called at all

Issue

I have 2 screens

  1. Screen A
  2. Screen B

I go from screen A to Screen B via the below code

    onTap: () {
      Navigator.pushNamed(context, '/screen_b');
    },

Screen B code

class ScreenB extends StatefulWidget {
  @override
  _ScreenBState createState() => _ScreenBState();
}

class _ScreenBState extends State<ScreenB> {

   SampleBloc sampleBloc;

    @override
    void initState() {
       super.initState();
       sampleBloc = BlocProvider.of<SampleBloc>(context);

       sampleBloc.stream.listen((state) {
          // this is getting called multiple times.
       }
    }

    @override
    void dispose() {
       super.dispose();
       sampleBloc.clear(); // If i add this, no event is firing from second time i come to the page. initState() is being called i checked so sampleBloc is not null.
    }
   
    
    @override
    Widget build(BuildContext context) {
       ....
           onTap: () {
              sampleBloc.add(CreateSampleEvent());
           },
       ....
    }

}

When i click tap to add CreateSampleEvent to the sampleBloc in Screen B, the ‘sampleBloc.stream.listen’ getting fired multiples times.

Sample outcome

Step 1. First do sampleBloc.add (tap) -> sampleBloc.stream.listen
fired one time
Step 2. Go back to Screen A and come back to
Screen B
Second go sampleBloc.add (tap) -> In one case i saw
the firing takes place in a pair of 2 times, and in other times the
firing takes place in pair of 4 times.

class SampleBloc extends Bloc<SampleEvent, SampleState> {
  final SampleRepository sampleRepository;

  SampleBloc({this.sampleRespository}) : super(null);


  @override
  Stream<SampleState> mapEventToState(SampleEvent event) async* {
    if (event is SampleEvent) {
      yield* mapSampleEventToState();
    }
  }

  Stream<SampleEvent> mapSampleEventToState() async* {
    yield SampleInProgressState();
    try {
      String sampleId = await sampleRepository.createSample();
      if (sampleId != null) {
        yield SampleCompletedState(sampleId, uid);
      } else {
        yield SampleFailedState();
      }
    } catch (e) {
      print('Error: $e');
      yield SampleFailedState();
    }
  }

Any ideas what might be going wrong ?

Solution

Since you are creating a manual subscription (by fetching the Bloc and then listening to a stream) you’ll also need to manually cancel that subscription when you’re done with it, otherwise, the SampleBloc will just keep getting new subscriptions and yielding events to all of them.

For that, you can either:

  1. Save the subscription and then cancel it in the dispose() method.
   SampleBloc sampleBloc;
   StreamSubscription _subscription;

    @override
    void initState() {
       super.initState();
       sampleBloc = BlocProvider.of<SampleBloc>(context);

       _subscription = sampleBloc.stream.listen((state) {
          // this is getting called multiple times.
       }
    }
    
   @override
   void dispose() {
     _subscription.cancel();
     super.dispose();
   }
  1. You can take advantage of the BlocListener from the package which is a widget that is disposed automatically, hence, removes the subscriptions it created.
BlocListener<BlocA, BlocAState>(
  listener: (context, state) {
    // do stuff here based on BlocA's state
  },
  child: Container(),
)

Answered By – Miguel Ruivo

Answer Checked By – Marilyn (FlutterFixes Volunteer)

Leave a Reply

Your email address will not be published.