Home > Software engineering >  How to use bloc initialised in one screen in a different screen
How to use bloc initialised in one screen in a different screen

Time:10-04

I have two screens in my flutter application Screen1 and Screen2. Screen1 is the home screen. I navigate from Screen1 to Screen2 via

Navigator.of(context).push(PageRouteBuilder<void>(pageBuilder: (context, animation, secondaryAnimation) => Screen2());

and Screen2 to Screen1 via

 Navigator.pop(context);

Screen1 is statelesswidget:

class Screen1 extends StatelessWidget {
    
    @override
    Widget build(BuildContext context) {
      return MultiBlocProvider(
         providers: [
            BlocProvider<BlocA>(create: (_) => BlocA()),
            BlocProvider<BlocB>(create: (_) => BlocB()),
          ]
         child: RaisedButton(
              child: Text('Goto Screen 2'),
              onPressed: Navigator.of(context).push(PageRouteBuilder<void>(pageBuilder: (context, animation, secondaryAnimation) => Screen2());
            ),
      )
    }
}

I would appreciate anyone can provide an answer that will satisfy the following :

  1. Want to access the two bloc initialised in the Screen1 from Screen2 using

    BlocProvider.value(value: BlocProvider.of(context), child: ...)

without bringing the initialisation of blocs upto the MaterialApp widget. Cannot make the MultiBlocProvider the parent of MaterialApp. I want the blocs only accessed in Screen1 and Screen2. It should not be accessed by other screens.

  1. Also when popped from Screen2 to Screen1, the blocs should not be disposed. Hence, continue to maintain state when popped from Screen2
  2. Should not pass the bloc via constructor or as arguments in Navigator

Currently getting following error:

flutter: ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
flutter: The following assertion was thrown building Screen2(dirty):
flutter:         BlocProvider.of() called with a context that does not contain a BlocA.
flutter:         No ancestor could be found starting from the context that was passed to
flutter: BlocProvider.of<BlocA>().
flutter:
flutter:         This can happen if the context you used comes from a widget above the BlocProvider.
flutter:
flutter:         The context used was: Screen2(dirty)
 
   

CodePudding user response:

you have to elevate MultiBlocProvider in the widget tree so that it wraps both screens, e.g. make it a parent of MaterialApp

CodePudding user response:

You can pass bloc elements as a parameter to Screen2

final blocAObject = BlocProvider.of<BlocA>(context);
Navigator.of(context).push(PageRouteBuilder<void>(pageBuilder: (context, animation, secondaryAnimation) => Screen2(bloca:blocAObject));

CodePudding user response:

If you're ok with initializing in MaterialApp while only having the blocs accessible from the two screens, try the following:

final blocA = BlocA(); // shared bloc instance
final blocB = BlocB(); // shared bloc instance

@override
Widget build(BuildContext context) {
  return MaterialApp(
    routes: {
      'screen1': (_) => MultiBlocProvider(
            providers: [
              BlocProvider(
                create: (context) => blocA,
              ),
              BlocProvider(
                create: (context) => blocB,
              ),
            ],
            child: Screen1(),
          ),
      'screen2': (_) => MultiBlocProvider(
            providers: [
              BlocProvider(
                create: (context) => blocA,
              ),
              BlocProvider(
                create: (context) => blocB,
              ),
            ],
            child: Screen2(),
          ),
    },
  );
}

CodePudding user response:

The use the already created bloc instance on new page, you can use BlocProvider.value.

Like passing BlocX to next route will be like

 Navigator.of(context).push(
      MaterialPageRoute(
        builder: (_) => BlocProvider.value(
          value: BlocProvider.of<BlocX>(context),
          child: Screen2(),
        ),
      ),
    );

I might go for repository provider on your case. But to pass multiple instance, you can wrap BlocProvider two times on route.

  Navigator.of(context).push(
  MaterialPageRoute(
    builder: (_) => BlocProvider.value(
      value: BlocProvider.of<BlocA>(context),
      child: BlocProvider.value(
        value: BlocProvider.of<BlocB>(context),
        child: Screen2(),
      ),
    ),
  ),
);

Currently, I cannot remember any better option, let me know if you've got any.

Now, your second route Screen2 can access both BlocB and BlocB instance.

You can get the instance it like, depend on your code structure.

BlocConsumer<BlocA, BlocAState>(
  builder: (context, state) {
    if (state is BlocAInitial) {
      return Text(state.name);
    }
    return Text("un impleneted");
  },
  listener: (context, state) {},
),

When you create bloc, and like to pass it with BlocProvider.value(value: BlocProvider.of<BlocA>(context),, you need to use separate context.

More about blocprovider.

Check the demo, It will clarify, I am using Builder instead of creating new widget for context.

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Screen1(),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return MultiBlocProvider(
      providers: [
        BlocProvider<BlocA>(create: (_) => BlocA()),
        BlocProvider<BlocB>(create: (_) => BlocB()),
      ],
      child: Builder(builder: (context) {
        return Scaffold(
          floatingActionButton: FloatingActionButton(
            onPressed: () {
              Navigator.of(context).push(
                MaterialPageRoute(
                  builder: (_) => BlocProvider.value(
                    value: BlocProvider.of<BlocA>(context),
                    child: BlocProvider.value(
                      value: BlocProvider.of<BlocB>(context),
                      child: Screen2(),
                    ),
                  ),
                ),
              );
            },
          ),
        );
      }),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          BlocConsumer<BlocA, BlocAState>(
            builder: (context, state) {
              if (state is BlocAInitial) {
                return Text(state.name);
              }
              return Text("un impleneted");
            },
            listener: (context, state) {},
          ),
          BlocConsumer<BlocB, BlocBState>(
            builder: (context, state) {
              if (state is BlocBInitial) {
                return Text(state.name);
              }
              return Text("un impleneted");
            },
            listener: (context, state) {},
          ),
        ],
      ),
    );
  }
}

Find more about flutterbloccoreconcepts

  • Related