felangel / bloc

A predictable state management library that helps implement the BLoC design pattern
https://bloclibrary.dev
MIT License
11.79k stars 3.39k forks source link

Flutter Bloc With Alert Dialogs throws an error #4032

Closed DiegoVega19 closed 10 months ago

DiegoVega19 commented 10 months ago

Description I am creating a SPA application with flutter_bloc which on a single screen, I use alerts to edit, add and perform certain actions, and if I open any of the alert dialog I receive the following error "The following ProviderNotFoundException was thrown building AlertContentWidget: Error: Could not find the correct Provider above this BlocBuilder<AlertsBloc, AlertsState> Widget",

Steps To Reproduce From here I attach a minimal reproducible example,

Main.dart

import 'package:bloc_alerts/bloc/alerts/alerts_bloc.dart';
import 'package:bloc_alerts/bloc/test/test_bloc.dart';
import 'package:bloc_alerts/dialogs.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});
  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return MultiBlocProvider(
        providers: [
          BlocProvider(create: (context) => TestBloc()),
          BlocProvider(create: (context) => AlertsBloc()),
        ],
        child: Scaffold(
          appBar: AppBar(
            backgroundColor: Theme.of(context).colorScheme.inversePrimary,
            title: Text(widget.title),
          ),
          body: BlocBuilder<TestBloc, TestState>(
            builder: (context, state) {
              if (state is TestInitial) {
                context.read<TestBloc>().add(const AddTest());
              }

              if (state is TestAdded) {
                return const Text('Hey');
              }
              return Container();
            },
          ),
          floatingActionButton: Builder(builder: (context) {
            return FloatingActionButton(
              onPressed: () {
                context.read<AlertsBloc>().add(const AddAlert());
                CustomDialogs.generalDialogForm(
                    context, const AlertContentWidget());
              },
              tooltip: 'Increment',
              child: const Icon(Icons.add),
            );
          }), // This trailing comma makes auto-formatting nicer for build methods.
        ));
  }
}

class CenterWidget extends StatelessWidget {
  final String title;
  const CenterWidget({
    super.key,
    required this.title,
  });

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Text(title),
          Text(
            'Hey',
            style: Theme.of(context).textTheme.headlineMedium,
          ),
        ],
      ),
    );
  }
}

class AlertContentWidget extends StatelessWidget {
  const AlertContentWidget({super.key});

  @override
  Widget build(BuildContext context) {
    return BlocBuilder<AlertsBloc, AlertsState>(
      builder: (context, state) {
        if (state is AlertsInitial) {
          return const Text('inicial');
        }
        if (state is AlertsAdded) {
          return const Text('added');
        }

        return const Text('hey');
      },
    );
  }
}

Bloc
import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
part 'alerts_event.dart';
part 'alerts_state.dart';

class AlertsBloc extends Bloc<AlertsEvent, AlertsState> {
  AlertsBloc() : super(AlertsInitial()) {
    on<AddAlert>((event, emit) {
        emit(AlertsAdded());
    });
  }
}

import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
part 'test_event.dart';
part 'test_state.dart';

class TestBloc extends Bloc<TestEvent, TestState> {
  TestBloc() : super(TestInitial()) {
    on<AddTest>((event, emit) {
      emit(TestAdded());
    });
  }
}

Alert Dialog Class

class CustomDialogs {
  static void generalDialogForm(BuildContext context, Widget child,
      { double width = 400}) {
    showDialog(
      context: context,
      builder: (_) => AlertDialog(
        backgroundColor: Colors.white,
        titlePadding: const EdgeInsetsDirectional.all(5),
        contentPadding: const EdgeInsetsDirectional.all(5),
        shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.circular(20.0)), //
        content: SizedBox(
          width: width,
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              Flexible(
                child: Column(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    const SizedBox(
                      height: 10,
                    ),
                   const Text('Header'),
                   const SizedBox(height: 60,),
                    child
                  ],
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Expected Behavior The alert dialog should show the corresponding data

Additional Context

The error is solved putting all the blocs in the main.dart, but I don't want to do that because there are many Screens and blocs and I want to create each bloc from each screen that uses it.

I hope you can help me, I have been trying to find a solution for days but nothing has worked for me, thank you very much in advance

chrismercredi commented 10 months ago

When developing a Flutter application using the BLoC pattern, it's crucial to manage the scope of your BLoCs/Cubits effectively. Each BLoC/Cubit should be available in the context where it's needed. Declaring all BLoCs/Cubits in your main method (root of the widget tree) can work, but it's not always the most efficient approach, especially for larger applications with multiple features or screens.

A more scalable and modular approach is to provide each BLoC/Cubit closer to where it's used. This strategy not only helps in organizing the code better but also ensures that the BLoCs/Cubits are disposed of properly when they are no longer needed, avoiding potential memory leaks and performance issues.

Example Implementation:

Here's an example of how you can wrap each page or feature in its own BlocProvider:

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'my_bloc.dart'; // Import your specific BLoC

/// Wrapper widget that provides the BLoC to MyPageView.
class MyPage extends StatelessWidget {
  const MyPage({super.key});

  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (context) => MyBloc(), // Replace with your specific BLoC
      child: const MyPageView(),
    );
  }
}

/// The page that uses the BLoC.
class MyPageView extends StatelessWidget {
  const MyPageView({super.key});

  @override
  Widget build(BuildContext context) {
    // Example usage of BlocBuilder
    return BlocBuilder<MyBloc, MyState>(
      builder: (context, state) {
        // Build your UI based on the BLoC state
        return Scaffold(
          appBar: AppBar(title: Text('My Page')),
          body: Center(
            child: Text('State: $state'),
          ),
        );
      },
    );
  }
}
codesculpture commented 10 months ago

@DiegoVega19 u need to move ur BlocProvider "up" one level in the widget tree, because floatingActionIcon and ur BlocProvider at same level, resulting the bloc not available to Builder's context on the floatingActionIcon.

DiegoVega19 commented 10 months ago

thank you guys, I managed to solve for yours advice

codesculpture commented 10 months ago

You can close the issue if its fixed.😀