Codenic Clean Architecture
  • Introduction
  • The Clean Architecture
    • Presentation Layer
    • Domain Layer
    • Infrastructure Layer
  • Tutorial
    • Overview
    • Creating a Flutter Modular Project
      • Tips for Managing a Modular Project
    • Implementing the Domain Layer
      • Core Dependencies
      • Creating an Entity
      • Creating a Failure
      • Creating a Repository Interface
      • Creating Use Cases
        • CRUD Operations (Runner)
        • Data Streams (Watcher)
    • Implementing the Infrastructure Layer
      • External Dependencies
      • Creating a Data Model
      • Creating a Data Source
      • Implementing a Repository
    • Implementing the Presentation Layer
      • External Dependencies
      • Dependency Injection and Service Locator
      • Widgets
        • Snackbar Handler
        • Global Blocs Widget
        • Note Widgets
  • Packages
    • Codenic Bloc Use Case
      • Runner
      • Watcher
    • Codenic Logger
      • Usage
      • Example
      • Modifying the Logger
      • Integrating Firebase Crashlytics to the logger
    • Codenic Exception Converter
      • Failure Object
      • Exception Converter
      • Exception Converter Suite
      • Example
Powered by GitBook
On this page
  • Global Blocs Widget
  • Personal Take: Use Case (Infrastructure Level) vs. Custom Bloc (Presentation Level)
  1. Tutorial
  2. Implementing the Presentation Layer
  3. Widgets

Global Blocs Widget

PreviousSnackbar HandlerNextNote Widgets

Last updated 2 years ago

Global Blocs Widget

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 and that will be used to create our (through the ) 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}
class MainApp extends StatelessWidget {
  /// {@macro MainApp}
  const MainApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      scrollBehavior: const MaterialScrollBehavior(),
      debugShowCheckedModeBanner: false,
      // TODO: To be created later
      home: const HomePage(),
      // 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 _GlobalBlocs extends StatelessWidget {
  const _GlobalBlocs({required this.child});

  final Widget child;

  @override
  Widget build(BuildContext context) {
    return MultiBlocProvider(
      providers: [
        // Lazily create the note CRUD use cases
        BlocProvider(
          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 entries
        BlocProvider(
          lazy: false,
          create: (context) => serviceLocator<WatchNoteEntries>()
            ..watch(params: const WatchNoteEntriesParams(limit: 10)),
        ),
      ],
      child: MultiBlocListener(
        listeners: [
          // Set up a listener for the note CRUD use cases
          BlocListener<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 is Running) {
      // A state that indicates that the use case is still in progress
      // In this example, do nothing
    } else if (state is RunFailed<Failure>) {
      SnackBarHandler.error(context, state.leftValue.message);
    } else if (state is RunSuccess) {
      SnackBarHandler.info(context, successMessage);

      context
          .read<FetchNoteEntries>()
          .run(params: const FetchNoteEntriesParams(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.

BlocProviders
BlocListeners
Use Cases
Service Locator