Listening of bloc getting called multiple times
Asked Answered
M

2

7

I have this code for listening to bloc on my screen.

late MyBloc myBloc;

@override
  void initState() {
    print('inside init state');
    super.initState();
    myBloc = BlocProvider.of<MyBloc>(context);
    myBloc.stream.listen((state) {
      if (state is MyAddCompletedState) {
        print('listening to bloc');
      }
    }
}

If I add an event, it will print listening to bloc once. If I go to another screen and return to this same screen, it will print listening to bloc twice. It seems like the first listen I did was still active. Next, I tried to close the bloc on my dispose thinking that it would stop the first listen. So that when I come back to the screen it will have a fresh listen but it will have an error: Bad state: Cannot add new events after calling close. I tried to research about this and some mention to dispose the bloc but it doesn't have that syntax anymore. Please help on how to properly close or stop it from listening once I have change screen. Thanks!

//this is found on my screen
late MyBloc myBloc;

@override
  void initState() {
    print('inside init state');
    super.initState();
    myBloc = BlocProvider.of<MyBloc>(context);
    myBloc.stream.listen((state) {
      if (state is MyAddCompletedState) {
        print('listening to bloc');
      }
    }
}

  @override
  void dispose() {
    myBloc.close();
    // myBloc.dispose(); --> I saw some tutorial to use this but it doesn't work
    super.dispose();
  }

This is on my main.dart:

    return FutureBuilder(
        future: InitFirst.instance.initialize(),
        builder: (context, AsyncSnapshot snapshot) {
          return MultiBlocProvider(
            providers: [
              BlocProvider<MyBloc>(
                create: (context) => MyBloc(
                  authenticationRepository: authenticationRepository,
                  userDataRepository: userDataRepository,
                ),
              ),
            ...

This is the part where I trigger the event. After this event run, the stream.listen will get triggered. But it will be triggered multiple times every time I visit the my screen.

    myBloc.add(MyAddEvent(
        selectedName,
        selectedCount);

Additional note: this event is triggering an update in Firebase which I need to check if it got completed that is why I do the stream.listen.

Morn answered 3/2, 2022 at 16:11 Comment(2)
We need more details. Why are you using stream listen in initState. Would you try to use BlocListener?Genevivegenevra
I just found a sample which uses stream.list in initState. I will check on that BlocListener.Morn
S
9

If the Stream used in Bloc keeps getting called when not in use, you may want to consider terminating the Stream with cancel() in your dispose() override. Try this

late MyBloc myBloc;
late StreamSubscription mSub;
    
@override
void initState() {
  print('inside init state');
  super.initState();
  myBloc = BlocProvider.of<MyBloc>(context);
  mSub = myBloc.stream.listen((state) {
    if (state is MyAddCompletedState) {
      print('listening to bloc');
    }
  }
}
    
@override
void dispose() {
  mSub.cancel();
        
  super.dispose();
}
Sanfo answered 6/2, 2022 at 11:53 Comment(1)
This is actually what I'm trying to find. A way to cancel my previous listen. Thanks a lot. It is now working as per my expectation.Morn
C
2

There are several ways to solve this issue depending on the context. I try to avoid using BLoC instantiation with StatefulWidgets. And, I like to use Cubits in connection with Observers, depending on the event entering my stream. I have added most of them in the following code, which isn't all used but for you to look at as a reference. My code example eliminates the issues that you describe. I would be happy to help further if you could provide a minimum viable code.

The following code is an example that I have put together to demonstrate a possible strategy. The BLoC package website heavily inspires the code. It has the standard counter app that we are all familiar with and navigation functionality.

Please see the following code to see if it helps at all:

Please be sure to add flutter_bloc: ^8.0.1 to your pubspec.yaml file.

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

void main() {
  BlocOverrides.runZoned(
    () => runApp(const MaterialApp(home: CounterPage())),
    blocObserver: CounterObserver(),
  );
}

class CounterObserver extends BlocObserver {
  @override
  void onChange(BlocBase bloc, Change change) {
    super.onChange(bloc, change);
    print('${bloc.runtimeType} $change');
  }

  @override
  void onTransition(Bloc bloc, Transition transition) {
    super.onTransition(bloc, transition);
    print('onTransition -- bloc: ${bloc.runtimeType}, transition: $transition');
  }

  @override
  void onError(BlocBase bloc, Object error, StackTrace stackTrace) {
    print('onError -- bloc: ${bloc.runtimeType}, error: $error');
    super.onError(bloc, error, stackTrace);
  }

  @override
  void onClose(BlocBase bloc) {
    super.onClose(bloc);
    print('onClose -- bloc: ${bloc.runtimeType}');
  }
}

class CounterCubit extends Cubit<int> {
  CounterCubit() : super(0);
  void increment() => emit(state + 1);
  void decrement() => emit(state - 1);
}

class ScoreCubit extends Cubit<int> {
  ScoreCubit() : super(0);
  void increment() => emit(state + 1);
  void decrement() => emit(state - 1);
}

class CounterPage extends StatelessWidget {
  const CounterPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MultiBlocProvider(
      providers: [
        BlocProvider<CounterCubit>(create: (context) => CounterCubit()),
        BlocProvider<ScoreCubit>(create: (context) => ScoreCubit()),
      ],
      child: const CounterView(),
    );
  }
}

class CounterView extends StatelessWidget {
  const CounterView({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Counter')),
      body: Column(
        children: [
          Center(
            child: BlocBuilder<ScoreCubit, int>(
              builder: (context, score) => Text('Score: $score'),
            ),
          ),
          Center(
            child: BlocBuilder<CounterCubit, int>(
              builder: (context, state) {
                return Text('$state',
                    style: Theme.of(context).textTheme.headline2);
              },
            ),
          ),
        ],
      ),
      floatingActionButton: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        crossAxisAlignment: CrossAxisAlignment.end,
        children: <Widget>[
          FloatingActionButton(
              heroTag: 'FAB1',
              child: const Icon(Icons.add),
              onPressed: () {
                // format from
                context.read<CounterCubit>().increment();
                context.read<ScoreCubit>().increment();
              }),
          const SizedBox(height: 8),
          FloatingActionButton(
            heroTag: 'FAB2',
            child: const Icon(Icons.remove),
            onPressed: () {
              context.read<CounterCubit>().decrement();
            },
          ),
          const SizedBox(height: 8),
          FloatingActionButton(
            heroTag: 'FAB3',
            child: const Icon(Icons.arrow_forward),
            onPressed: () {
              Navigator.of(context).push(
                MaterialPageRoute(
                  builder: (context) => Scaffold(
                    appBar: AppBar(title: const Text('Next Page')),
                    body: const Center(
                      child: Text('This is the next page'),
                    ),
                  ),
                ),
              );
            },
          ),
        ],
      ),
    );
  }
}

Like all things in Flutter, this, of course, is only one strategy of many.

Codify answered 6/2, 2022 at 5:31 Comment(4)
I have added more of my code. But I will also try your suggestion. Also, I'm already using the latest flutter_bloc ^8.0.1Morn
I tested your code just now and it works fine. But I'm not sure on how will I implement it in my code. Actually, I initially thought that the answer to my question might just be simple since I start the listen which doesn't end on listening even if I go to another screen. I thought that I'm just missing a syntax where I can implement in my dispose method so that the first listen can be ended.Morn
Yes, I feel the same. That is why I just want to dispose/cancel/terminate the previous one. But I saw a previous tip that I should put my bloc not on the highest level. I guess that tip is also from the old version.Morn
Thanks. One of the answer works well for me.Morn

© 2022 - 2024 — McMap. All rights reserved.