In this next step, we will create the root widget of the app which will include a _GlobalBlocs widget.
The _GlobalBlocs widget will contain several BlocProviders and BlocListeners that will be used to create our Use Cases (through the Service Locator) and monitor any changes in the state of the Use Case.
Note that the _GlobalBlocs widget is placed within the MaterialApp.builder method. This ensures that the use cases are attached to the MaterialApp and can be accessed by any child widgets within the widget tree.
To begin, create a file called main_app.dart in the note_app/modules/presentation/lib/ directory and copy the following code:
import'package:domain/domain.dart';import'package:domain/note/use_cases/create_note_entry.dart';import'package:domain/note/use_cases/delete_note_entry.dart';import'package:domain/note/use_cases/fetch_note_entries.dart';import'package:domain/note/use_cases/update_note_entry.dart';import'package:domain/note/use_cases/watch_note_entries.dart';import'package:flutter/material.dart';import'package:flutter_bloc/flutter_bloc.dart';import'package:presentation/common/snackbar_handler.dart';import'package:presentation/note/pages/home_page.dart';import'package:presentation/presentation.dart';/// {@template MainApp}/// The root widget of the app./// {@endtemplate}classMainAppextendsStatelessWidget {/// {@macro MainApp}constMainApp({super.key});@overrideWidgetbuild(BuildContext context) {returnMaterialApp( scrollBehavior:constMaterialScrollBehavior(), debugShowCheckedModeBanner:false,// TODO: To be created later home:constHomePage(),// The Blocs are placed here so that they can be accessed by any// widgets in the widget tree builder: (context, child) =>_GlobalBlocs(child: child!), ); }}/// {@template _GlobalBlocs}/// A widget contianing the Bloc providers and listeners for the global Bloc/// instances./// {@endtemplate}class_GlobalBlocsextendsStatelessWidget {const_GlobalBlocs({required this.child});finalWidget child;@overrideWidgetbuild(BuildContext context) {returnMultiBlocProvider( providers: [// Lazily create the note CRUD use casesBlocProvider( create: (context) =>serviceLocator<CreateNoteEntry>(), ),BlocProvider( create: (context) =>serviceLocator<FetchNoteEntries>(), ),BlocProvider( create: (context) =>serviceLocator<UpdateNoteEntry>(), ),BlocProvider( create: (context) =>serviceLocator<DeleteNoteEntry>(), ),// Eagerly create and start the [WatchNoteEntries] use// case to start streamining the 10 recent note entriesBlocProvider( lazy:false, create: (context) =>serviceLocator<WatchNoteEntries>() ..watch(params:constWatchNoteEntriesParams(limit:10)), ), ], child:MultiBlocListener( listeners: [// Set up a listener for the note CRUD use casesBlocListener<CreateNoteEntry, RunnerState>( listener: (context, createNoteEntryState) =>_handleRunnerState( context, createNoteEntryState,'Note entry created', ), ),BlocListener<UpdateNoteEntry, RunnerState>( listener: (context, updateNoteEntryState) =>_handleRunnerState( context, updateNoteEntryState,'Note entry updated', ), ),BlocListener<DeleteNoteEntry, RunnerState>( listener: (context, deleteNoteEntryState) =>_handleRunnerState( context, deleteNoteEntryState,'Note entry deleted', ), ), ], child: child, ), ); }/// Handles the given [state]. /// /// If [state] is a [RunFailed] then an error snackbar is displayed. /// If [state] is a [RunSuccess] then a success snackbar is displayed and the /// note entries pagination is refreshed.void_handleRunnerState(BuildContext context,RunnerState state,String successMessage, ) {if (state isRunning) {// A state that indicates that the use case is still in progress// In this example, do nothing } elseif (state isRunFailed<Failure>) {SnackBarHandler.error(context, state.leftValue.message); } elseif (state isRunSuccess) {SnackBarHandler.info(context, successMessage); context .read<FetchNoteEntries>() .run(params:constFetchNoteEntriesParams(pageToken:0)); } }}
Personal Take: Use Case (Infrastructure Level) vs. Custom Bloc (Presentation Level)
Personal Take: Do I still need to create custom Blocs for my widgets if Use Cases are already Bloc objects with state management capabilities?
The decision to use a Use Case or a custom Bloc for state management will depend on the specific needs of your application and the complexity of the data you need to manage. If you need to manage ephemeral data at the presentation level, it may be more appropriate to use a custom Bloc, whereas Use Cases are better suited for reacting to data at the infrastructure level. For example, if you have a widget with complex properties that need to be accessed by multiple widgets, it may be more appropriate to create a dedicated Bloc to manage the state of that widget.