Bloc: Architectural question - BlocListener

Created on 17 Dec 2019  路  2Comments  路  Source: felangel/bloc

I have declared all my blocs in main.dart with MultiBlocProvider. One of my Blocs is a SharedStoreBloc with AddItemEvent and LoadItemEvent. I use this to store temporary items such as document id's of documents, that I will use again throughout my app.

In the example below:

  • SharedStoreBloc is created in main.dart
  • BlocListener is used to listen for the SharedStoreItemLoadedState, to get the value in both the course_view.dart and section_view.dart file.

My problem is, that when I fire the SharedStoreItemLoadEvent in section_view.dart, both BlocListeners in both the views will fire.

As the value I get in section_view class is a different one, then in the course_view class, the course_view class fails, as it received the value loaded in the section_view class, as both receive the same state.

One option would be, to move the creation of the SharedStoreBloc from main.dart into each the section_view.dart, and course_view.dart. I assume that then this would not trigger both BlocListeners in both classes to fire. But I would have to create the SharedStoreBloc in each of the classes I'm using it, which wouldn't be a very nice solution.

Is there a better way to make sure, only the BlocListener receives the state, within the class the event is triggered?

Below the snippets of the code.

main.dart

void main() async {
  BlocSupervisor.delegate = SimpleBlocDelegate();
  runApp(TeachApp());
}

class TeachApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MultiBlocProvider(
      providers: [
        BlocProvider<AuthenticationBloc>(
          create: (context) {
            return AuthenticationBloc(
              userRepository: FirebaseUserRepository(),
            )..add(AppStarted());
          },
        ),
        BlocProvider<SchoolBloc>(
          create: (context) {
            return SchoolBloc(
              schoolRepository: FirebaseSchoolRepository(),
            );
          },
        ), 
        BlocProvider<CourseBloc>(
          create: (context) {
            return CourseBloc(
              courseRepository: FirebaseCourseRepository(),
            );
          },
        ), 
        BlocProvider<SectionBloc>(
          create: (context) {
            return SectionBloc(
              sectionRepository: FirebaseSectionRepository(),
            );
          },
        ),
        BlocProvider<SharedStoreBloc>(
          create: (context) {
            return SharedStoreBloc(
              sharedStoreRepository: SharedDbRepository(),
            );
          },
        ),
      ],
      child: MaterialApp(
      ...

course_view.dart

class CourseView extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _CourseViewState();
}

class _CourseViewState extends State<CourseView> {
  SharedStoreBloc _sharedStoreBloc;
  CourseBloc _courseBloc;

  @override
  void initState() {
    // Assign the blocs to the variables
    _sharedStoreBloc = BlocProvider.of<SharedStoreBloc>(context);
    _courseBloc = BlocProvider.of<CourseBloc>(context);

    // Dispatch events on page load
    _sharedStoreBloc.add(SharedStoreItemAddEvent(PrefKeys.schoolId, "sfjklsdjklsfd"));

     // FIRE THE LOAD EVENT, WHICH WILL YIELD SHAREDSTOREITEMLOADEDSTATE
     _sharedStoreBloc.add(SharedStoreItemLoadEvent(PrefKeys.schoolId));

    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    // build ui
    return Scaffold(
      appBar: AppBar(
        title: Text("Courses")
      ),
      body: BlocListener(
        bloc: _sharedStoreBloc,
        listener: (context, state) {
          // THIS IS WHERE STUFF FAILS
          if(state is SharedStoreItemLoadedState) {
            // load the courses assigned to the school
            _courseBloc.add(LoadCoursesOfSchoolEvent(state.value));          
          }
        },
        child: BlocBuilder<CourseBloc, CourseState>(
          bloc: _courseBloc,
          builder: (context, state) {
            if(state is CoursesLoadedState) {

section_view.dart

class SectionView extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _SectionViewState();
}

class _SectionViewState extends State<SectionView> {
  SharedStoreBloc _sharedStoreBloc;
  SectionBloc _sectionBloc;

  @override
  initState(){
    _sharedStoreBloc = BlocProvider.of<SharedStoreBloc>(context);
    _sectionBloc = BlocProvider.of<SectionBloc>(context);

    // FIRE THE LOAD EVENT, WHICH WILL YIELD SHAREDSTOREITEMLOADEDSTATE
    _sharedStoreBloc.add(SharedStoreItemLoadEvent(PrefKeys.courseId));
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Sections")
      ),
      body: Text("here we are")
    );
  }
question

Most helpful comment

When course and section fire the SharedStoreItemLoadEvent with different data and they expect different data, could it be the you actually need two different SharedStores? One for each of course and section?

As I understand, Blocs are there for exactly this purpose: Sharing the same state between different components. If you start to distinguish between items in the Listener (with a condition as in https://pub.dev/documentation/flutter_bloc/latest/flutter_bloc/BlocListener-class.html), it smells a little like you need to divide SharedStore.

All 2 comments

When course and section fire the SharedStoreItemLoadEvent with different data and they expect different data, could it be the you actually need two different SharedStores? One for each of course and section?

As I understand, Blocs are there for exactly this purpose: Sharing the same state between different components. If you start to distinguish between items in the Listener (with a condition as in https://pub.dev/documentation/flutter_bloc/latest/flutter_bloc/BlocListener-class.html), it smells a little like you need to divide SharedStore.

Thanks Valentin for your answer. Much appreciated. I've read what I can do with "condition", but didn't see, how I could use it in my use-case . Therefore agreed, looks like dividing the shared store is the way to go. I hoped I can prevent some boilerplate code, but it's fine, if this is considered to be the best practice. Will close this issue.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

hivesey picture hivesey  路  3Comments

abinvp picture abinvp  路  3Comments

MahdiPishguy picture MahdiPishguy  路  3Comments

craiglabenz picture craiglabenz  路  3Comments

Reidond picture Reidond  路  3Comments