7

Im using Flutter and flutter_bloc for a small app, and i used MultiBlocProvider to use multiple BlocProviders that i need in the main home page, and under the home page, there is a MainWidget, which can access the given Bloc easily by: BlocProvider.of<OldBloc>(context) The MainWidget calls NewWidget as a dialog by: showDialog(context: context, builder: (context) => NewWidget()) The problem is, i cannot access OldBloc from NewWidget(), so i assumed that MainWidget isnt passing its context to NewWidget when using showDialog ?

HomeScreen.dart

import 'package:flutter/material.dart';

class HomeScreen extends StatelessWidget {
  const HomeScreen({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: MultiBlocProvider(
      providers: [
        BlocProvider(
          create: (context) => OldBloc()..add(Initialize()),
        ),
        BlocProvider(
          create: (context) => OtherBloc()..add(Initialize()),
        ),
      ],
      child: Stack(
        children: [
          MainWidget(),
          MenuWidget(),
        ],
      ),
    ));
  }
}

MainWidget.dart

import 'package:flutter/material.dart';

class MainWidget extends StatelessWidget {
  const MainWidget({
    Key key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return TextField(onTap: () => showDialog(context: context, builder: (context) => NewWidget()));
  }
}

NewWidget.dart

import 'package:flutter/material.dart';

class NewWidget extends StatelessWidget {
  const NewWidget({Key key}) : super(key: key);

  @override
  Widget build(context) {
    return Text(BlocProvider.of<OldBloc>(context).name); // <------- THIS GIVES AN ERROR THAT IT CANT FIND A BLOC OF THE TYPE OldBloc
  }
}
RedZ
  • 857
  • 1
  • 13
  • 25

3 Answers3

12

You can simply use this (suggested by Flex Angelov):

class SimpleWidget extends StatelessWidget {
  const SimpleWidget({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (_) => FilesBloc(),
      child: Builder(
        builder: (builderContext) => TextButton(
          onPressed: () => _onTapSubmit(builderContext),
          child: Text('Submit'),
        ),
      ),
    );
  }

  void _onTapSubmit(BuildContext builderContext) {
    showDialog(
      context: builderContext,
      builder: (_) {
        return BlocProvider.value(
          value: builderContext.read<FilesBloc>(),
          child: const MyDialogWidget(),
        );
      },
    );
  }
}`

You can replace Builder with a BlocBuilder.

Mostafa Fahimi
  • 510
  • 1
  • 8
  • 23
  • I had to also wrap my child dialog in a stateful builder as suggested in another answer by Simon Sot. Not sure why it didn't work without it. – Dylan Cross Oct 14 '22 at 16:58
4

You have no access to BuildContext in your showDialog method, documentation:

The widget returned by the builder does not share a context with the location that showDialog is originally called from.

The context argument is used to look up the Navigator and Theme for the dialog. It is only used when the method is called.

Its also recommended to use a StatefulBuilder or a custom StatefulWidget, here is an example.

Simon Sot
  • 2,748
  • 2
  • 10
  • 23
  • Is passing the bloc as an argument a good solution ? – RedZ May 07 '21 at 12:42
  • I'd personally would try to explicitly pass BuildContext as a final argument and use it in your bloc calls, or (if by any case that is ok for you) try to provide bloc before your MaterialApp to be a global scoped, not really sure if this still will work with showDialog context, but worth a try anyway. – Simon Sot May 07 '21 at 13:01
  • 1
    I just tried to pass BuildContext and tried passinng BlocProvider and both of them worked, but i settled with passing a BuildContext, i found it better than using a global scoped bloc, thank you for your help :) – RedZ May 07 '21 at 16:49
0

I solved this problem.

I use cubit instead of bloc but it doesn't matter.

ShowDialog doesn't pass parent context. So you should create a new cubit and pass your parent cubit and state in arguments for a new cubit. I called main cubit FooCubit, new cubit FooCubitWrapper and DialogWidget is child widget that needs some BLOC logic in showDialog.


    var fooCubit = context.read<FooCubit>(); // your parrent cubit and state
    return BlocBuilder<FooCubit , FooState>(
        builder: (_, state) {
.
.///some logic///
.
          showDialog(
            context: context,
            builder: (_) => // here you have to create new cubit 
                BlocProvider( // because there is no access to parent cubit
                  create: (_) => FooCubitWrapper(cubit, state),
                  child: DialogWidget(),
                ),
          );

This is FooCubitWrapper. For example, we need the boo method from the parent cubit. So we need to create here boo method and inside we need to reference to parent boo method and emit parent state.

class FooCubitWrapper extends Cubit<FooState> {
  FooCubit fooCubit;

  FooCubitWrapper(this.fooCubit, FooState initialState) : super(initialState);

  boo() {
    fooCubit.boo();

    emit(fooCubit.state);
  }
}

And finally, in your DialogWidget, you do all like usual

    var cubit = context.read<TagsCubitWrapper>();
    return BlocBuilder<TagsCubitWrapper, TagsState>(
        builder: (context, state) {

// work with methods and fields as usual
    cubit.boo();
    if !(state.someField) {}

});