inter cretasyo
inter cretasyo

Reputation: 179

Listening of bloc getting called multiple times

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.

Upvotes: 7

Views: 11301

Answers (2)

Artem Zelinskiy
Artem Zelinskiy

Reputation: 2210

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();
}

Upvotes: 9

George Lee
George Lee

Reputation: 882

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.

Upvotes: 2

Related Questions