Skip to content

Instantly share code, notes, and snippets.

@felangel
Last active December 30, 2023 10:52
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save felangel/6bcd4be10c046ceb33eecfeb380135dd to your computer and use it in GitHub Desktop.
Save felangel/6bcd4be10c046ceb33eecfeb380135dd to your computer and use it in GitHub Desktop.
[flutter_bloc_recipes] Navigation: Routes
import 'package:flutter/material.dart';
import 'package:meta/meta.dart';
import 'package:bloc/bloc.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
void main() {
runApp(
BlocProvider(
builder: (context) => MyBloc(),
child: MyApp(),
),
);
}
enum MyEvent { eventA, eventB }
@immutable
abstract class MyState {}
class StateA extends MyState {}
class StateB extends MyState {}
class MyBloc extends Bloc<MyEvent, MyState> {
@override
MyState get initialState => StateA();
@override
Stream<MyState> mapEventToState(MyEvent event) async* {
switch (event) {
case MyEvent.eventA:
yield StateA();
break;
case MyEvent.eventB:
yield StateB();
break;
}
}
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
routes: {
'/': (context) => PageA(),
'/pageB': (context) => PageB(),
},
initialRoute: '/',
);
}
}
class PageA extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocListener<MyBloc, MyState>(
listener: (context, state) {
if (state is StateB) {
Navigator.of(context).pushNamed('/pageB');
}
},
child: Scaffold(
appBar: AppBar(
title: Text('Page A'),
),
body: Center(
child: RaisedButton(
child: Text('Go to PageB'),
onPressed: () {
BlocProvider.of<MyBloc>(context).add(MyEvent.eventB);
},
),
),
),
);
}
}
class PageB extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Page B'),
),
body: Center(
child: RaisedButton(
child: Text('Pop'),
onPressed: () {
Navigator.of(context).pop();
},
),
),
);
}
}
@abn-r
Copy link

abn-r commented Jul 30, 2019

I user flutter_bloc : 0.20.0, copy and paste exactly the code... help pls D:

I/flutter (20502): BlocProvider.of() called with a context that does not contain a Bloc of type MyBloc.
I/flutter (20502): No ancestor could be found starting from the context that was passed to
I/flutter (20502): BlocProvider.of().
I/flutter (20502):
I/flutter (20502): This can happen if:
I/flutter (20502): 1. The context you used comes from a widget above the BlocProvider.
I/flutter (20502): 2. You used MultiBlocProvider and didn't explicity provide the BlocProvider types.
I/flutter (20502):
I/flutter (20502): Good: BlocProvider(builder: (context) => MyBloc())
I/flutter (20502): Bad: BlocProvider(builder: (context) => MyBloc()).
I/flutter (20502):
I/flutter (20502): The context used was: PageA(dirty)

@felangel
Copy link
Author

Hi @ockre 👋
I just re-tested and was unable to reproduce the issue you're seeing. Can you please double-check that you're using flutter_bloc 0.20.0 and that the code is identical? Thanks!

@abn-r
Copy link

abn-r commented Jul 30, 2019

Oh I'm so sorry, what a shame, I hadn't downloaded the flutter_bloc packages, I had the old version, an apology and thanks for the reply.

Thank you @felangel

@felangel
Copy link
Author

@ockre no problem! Glad it all works now 😄

@Addeuz
Copy link

Addeuz commented Feb 12, 2020

@felangel

Hello, I'm trying to use this implementation for an AppDrawer that I have created.

This is what my drawer looks like:

class AppDrawer extends StatefulWidget {
  Map<String, dynamic> user;
  bool hasData = false;
  final secureStorage = new FlutterSecureStorage();
  var _userData;

  @override
  _AppDrawerState createState() => _AppDrawerState();
}

class _AppDrawerState extends State<AppDrawer> {
  void getUserData() async {
    String jsonString;
    Map<String, dynamic> userData;
    widget.secureStorage.read(key: 'userData').then((value) {
      jsonString = value;
      userData = jsonDecode(jsonString);
      setState(() {
        widget._userData = userData['iam'];
        widget.hasData = true;
      });
    });
  }

  @override
  void initState() {
    super.initState();
    getUserData();
  }

  @override
  Widget build(BuildContext context) {
    return BlocProvider<NavigationBloc>(
      create: (context) => NavigationBloc(),
      child: BlocListener<NavigationBloc, NavigationState>(
        listener: (context, navigationState) {
          if (navigationState is TodayState) {
            Navigator.of(context).pushNamed(Routes.today);
          }
          if (navigationState is CalendarState) {
            Navigator.of(context).pushNamed(Routes.calendar);
          }
        },
        child: Drawer(
          child: Container(
            color: Color.fromRGBO(40, 55, 68, 1.0),
            child: ListView(
              padding: EdgeInsets.zero,
              children: <Widget>[
                _createHeader(),
                ListTile(
                  title: Row(
                    children: <Widget>[
                      Icon(
                        Icons.today,
                        color: Color.fromRGBO(125, 214, 222, 1.0),
                      ),
                      Padding(
                        padding: EdgeInsets.only(left: 30.0),
                        child: Text(
                          'Idag',
                          style: TextStyle(color: Colors.white),
                        ),
                      ),
                    ],
                  ),
                  onTap: () => BlocProvider.of<NavigationBloc>(context)
                      .add(NavigationEvent.TodayEvent),
                ),
                ListTile(
                  title: Row(
                    children: <Widget>[
                      Icon(
                        Icons.calendar_today,
                        color: Color.fromRGBO(125, 214, 222, 1.0),
                      ),
                      Padding(
                        padding: EdgeInsets.only(left: 30.0),
                        child: Text(
                          'Kalender',
                          style: TextStyle(color: Colors.white),
                        ),
                      ),
                    ],
                  ),
                  onTap: () => BlocProvider.of<NavigationBloc>(context)
                      .add(NavigationEvent.CalendarEvent),
                ),
                Divider(
                  color: Color.fromRGBO(93, 96, 99, 1.0),
                ),
                ListTile(
                  title: Row(
                    children: <Widget>[
                      Icon(
                        Icons.exit_to_app,
                        color: Color.fromRGBO(125, 214, 222, 1.0),
                      ),
                      Padding(
                        padding: EdgeInsets.only(left: 30.0),
                        child: Text(
                          'Logga ut',
                          style: TextStyle(color: Colors.white),
                        ),
                      ),
                    ],
                  ),
                  onTap: () => BlocProvider.of<AuthenticationBloc>(context)
                      .add(LoggedOut()),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }

And when I try to click a ListTile I get this error:

The following assertion was thrown while handling a gesture:
        BlocProvider.of() called with a context that does not contain a Bloc of type NavigationBloc.

        No ancestor could be found starting from the context that was passed to BlocProvider.of<NavigationBloc>().

        This can happen if the context you used comes from a widget above the BlocProvider.

        The context used was: AppDrawer(state: _AppDrawerState#fbaa0)
        
When the exception was thrown, this was the stack: 
#0      BlocProvider.of (package:flutter_bloc/src/bloc_provider.dart:106:7)
#1      _AppDrawerState.build.<anonymous closure> (package:project_claystone/resources/drawer.dart:70:45)
#2      _InkResponseState._handleTap (package:flutter/src/material/ink_well.dart:705:14)
#3      _InkResponseState.build.<anonymous closure> (package:flutter/src/material/ink_well.dart:788:36)
#4      GestureRecognizer.invokeCallback (package:flutter/src/gestures/recognizer.dart:182:24)
...
Handler: "onTap"
Recognizer: TapGestureRecognizer#d5747
  debugOwner: GestureDetector
  state: ready
  won arena
  finalPosition: Offset(153.3, 243.7)
  finalLocalPosition: Offset(153.3, 38.7)
  button: 1
  sent tap down
════════════════════════════════════════════════════════════════════════════════════════════════════

My logout button works. But previously I used pushReplacementNamed to change my routes and I couldn't log out if I changed the route, I read something you wrote that it had to do with the "Replacement" part and that was how I found this bloc oriented navigation method.

I know that my routing works because when I write

create: (context) => NavigationBloc()..add(NavigationEvent.TodayEvent),

in the BlocProvider it automatically routes me to the TodaysPage as soon as I open the drawer.

I get this response from my BlocSupervisor:

flutter: Event: NavigationEvent.TodayEvent
flutter: Transistion: Transition { currentState: Instance of 'TodayState', event: NavigationEvent.TodayEvent, nextState: Instance of 'TodayState' }

I know that I'm really not supposed to use blocs in a StatefulWidget but I need to load data with initState(), and I don't know any other way of getting data into a StatelessWidget (guess I can use blocs but don't want to refactor atm).

What can the solution be?

@Allan-Nava
Copy link

Any examples for bottomNavigationBar and routing? I'm trying to implement a complex app

@Rammehar
Copy link

Any examples for bottomNavigationBar and routing? I'm trying to implement a complex app

I am also searching for the same

@felangel
Copy link
Author

What issues are you facing with BottomNavigatiomBar?

@Rammehar
Copy link

Rammehar commented Apr 16, 2020 via email

@hackerunet
Copy link

hackerunet commented May 20, 2020

I have just also started a project and I'm trying to the same, do you know if your approach went well?? @Rammehar

@Allan-Nava
Copy link

I have just also started a project and I'm trying to the same, do you know if your approach went well?? @Rammehar

Anyone knows how to have 2 BLoC inside one stateful widget?

@hackerunet
Copy link

hackerunet commented May 21, 2020 via email

@Allan-Nava
Copy link

I think the Best approach is MultiBlocProvider El jue., 21 de mayo de 2020 12:58 a. m., Allan Nava < notifications@github.com> escribió:

@Allan-Nava commented on this gist. ------------------------------ I have just also started a project and I'm trying to the same, do you know if your approach went well?? @Rammehar https://github.com/Rammehar Anyone knows how to have 2 BLoC inside one stateful widget? — You are receiving this because you commented. Reply to this email directly, view it on GitHub https://gist.github.com/6bcd4be10c046ceb33eecfeb380135dd#gistcomment-3312431, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAG3DKEEEDBI7PPPSI42CULRSS7GXANCNFSM4IHYD45A .

I already used the multiblocprovider but when I initialize a new page, I need the events api and categories api

@hackerunet
Copy link

hackerunet commented May 21, 2020 via email

@Allan-Nava
Copy link

Inject both api's to the constructor of the Bloc. Or just add both api's on each bloc using a repository pattern. El jue., 21 de mayo de 2020 6:41 a. m., Allan Nava notifications@github.com escribió:

@Allan-Nava commented on this gist. ------------------------------ I think the Best approach is MultiBlocProvider El jue., 21 de mayo de 2020 12:58 a. m., Allan Nava < @.***> escribió: … <#m_-2991634695931115746_> @Allan-Nava https://github.com/Allan-Nava commented on this gist. ------------------------------ I have just also started a project and I'm trying to the same, do you know if your approach went well?? @Rammehar https://github.com/Rammehar https://github.com/Rammehar Anyone knows how to have 2 BLoC inside one stateful widget? — You are receiving this because you commented. Reply to this email directly, view it on GitHub https://gist.github.com/6bcd4be10c046ceb33eecfeb380135dd#gistcomment-3312431, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAG3DKEEEDBI7PPPSI42CULRSS7GXANCNFSM4IHYD45A . I already used the multiblocprovider but when I initialize a new page, I need the events api and categories api — You are receiving this because you commented. Reply to this email directly, view it on GitHub https://gist.github.com/6bcd4be10c046ceb33eecfeb380135dd#gistcomment-3312758, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAG3DKAMTPQQQWZTHVZXZRTRSUHN7ANCNFSM4IHYD45A .

I already use the repository, but when I'm trying to get all values inside a page it crash

@Allan-Nava
Copy link

Inject both api's to the constructor of the Bloc. Or just add both api's on each bloc using a repository pattern. El jue., 21 de mayo de 2020 6:41 a. m., Allan Nava notifications@github.com escribió:

@Allan-Nava commented on this gist. ------------------------------ I think the Best approach is MultiBlocProvider El jue., 21 de mayo de 2020 12:58 a. m., Allan Nava < @.***> escribió: … <#m_-2991634695931115746_> @Allan-Nava https://github.com/Allan-Nava commented on this gist. ------------------------------ I have just also started a project and I'm trying to the same, do you know if your approach went well?? @Rammehar https://github.com/Rammehar https://github.com/Rammehar Anyone knows how to have 2 BLoC inside one stateful widget? — You are receiving this because you commented. Reply to this email directly, view it on GitHub https://gist.github.com/6bcd4be10c046ceb33eecfeb380135dd#gistcomment-3312431, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAG3DKEEEDBI7PPPSI42CULRSS7GXANCNFSM4IHYD45A . I already used the multiblocprovider but when I initialize a new page, I need the events api and categories api — You are receiving this because you commented. Reply to this email directly, view it on GitHub https://gist.github.com/6bcd4be10c046ceb33eecfeb380135dd#gistcomment-3312758, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAG3DKAMTPQQQWZTHVZXZRTRSUHN7ANCNFSM4IHYD45A .

Can we discuss in other platform? :)

@hackerunet
Copy link

hackerunet commented May 21, 2020 via email

@Allan-Nava
Copy link

Add the crash error.. i prefer using this Platform to record every issue founded. For future generations... El jue., 21 de mayo de 2020 6:52 a. m., Allan Nava notifications@github.com escribió:

@Allan-Nava commented on this gist. ------------------------------ Inject both api's to the constructor of the Bloc. Or just add both api's on each bloc using a repository pattern. El jue., 21 de mayo de 2020 6:41 a. m., Allan Nava @.*** escribió: … <#m_3649620967552453051_> @Allan-Nava https://github.com/Allan-Nava commented on this gist. ------------------------------ I think the Best approach is MultiBlocProvider El jue., 21 de mayo de 2020 12:58 a. m., Allan Nava < @.***> escribió: … <#m_-2991634695931115746_> @Allan-Nava https://github.com/Allan-Nava https://github.com/Allan-Nava https://github.com/Allan-Nava commented on this gist. ------------------------------ I have just also started a project and I'm trying to the same, do you know if your approach went well?? @Rammehar https://github.com/Rammehar https://github.com/Rammehar https://github.com/Rammehar Anyone knows how to have 2 BLoC inside one stateful widget? — You are receiving this because you commented. Reply to this email directly, view it on GitHub https://gist.github.com/6bcd4be10c046ceb33eecfeb380135dd#gistcomment-3312431, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAG3DKEEEDBI7PPPSI42CULRSS7GXANCNFSM4IHYD45A . I already used the multiblocprovider but when I initialize a new page, I need the events api and categories api — You are receiving this because you commented. Reply to this email directly, view it on GitHub https://gist.github.com/6bcd4be10c046ceb33eecfeb380135dd#gistcomment-3312758, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAG3DKAMTPQQQWZTHVZXZRTRSUHN7ANCNFSM4IHYD45A . Can we discuss in other platform? :) — You are receiving this because you commented. Reply to this email directly, view it on GitHub https://gist.github.com/6bcd4be10c046ceb33eecfeb380135dd#gistcomment-3312773, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAG3DKEI3ETVSYLPOXXFT7TRSUIZRANCNFSM4IHYD45A .

Okay wait, I don't have any error. But It freeze and crash


class _AppPageState extends State<AppPage> {
  ///
  int currentIndex      = 0;
  ///
  final List<Widget> pageList = List<Widget>();
  ///
  @override
  void initState() {
    ///
    pageList.add(HomePage());
    pageList.add(EventsPage());
    pageList.add(ProfilePage());
    ///
    super.initState();
  }
  ///
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: widget.appBar,
      body: IndexedStack(
        index: currentIndex,
        children: pageList,
      ),
      bottomNavigationBar: BottomNavigationBarStateless(
          onTapNavigation: this._onItemTapped,
          currentIndex: currentIndex,
      ),
    );
  }
  ///
  void _onItemTapped(int index) {
    //print("_onItemTapped $index");
    setState(() {
      currentIndex = index;
    });
  }
  ///
}

in this page I need the categories list


///
class _EventsPageState extends State<EventsPage> {
  ///
  Completer<void> _refreshCompleter;
  ScrollController _scrollController;
  List<LiveEvents> liveEvents = [];
  String nextUrlLiveEvents    = '';
  static DateTime today       = new DateTime.now();
  var dateFilter              = dateFormatter.format( today );
  List<String> scheduledDays  = [];
  ///
  @override
  void initState() {
    super.initState();
    _refreshCompleter = Completer<void>();
    ///
    print("dateFilter $dateFilter");
    ///
    BlocProvider.of<LiveEventBloc>(context)
                    .add( FetchLiveEvents( eventPage: '', date: dateFilter )  );
    ///
    
    //var fiftyDaysFromNow = today.add(new Duration(days: 50));
    scheduledDays.add(dateFilter);
    ///
    for(var i = 1; i <= 7; i++){
      var newDay = today.add(new Duration(days: i));
      scheduledDays.add( dateFormatter.format( newDay ) );
    }
    ///
    _scrollController = ScrollController();
    _scrollController.addListener(_scrollListener);
    ///
  }
  ///
  void _scrollListener() {
    if (_scrollController.offset >= _scrollController.position.maxScrollExtent &&
        !_scrollController.position.outOfRange) {
        print("reach the bottom");
        //
        //BlocProvider.of<LiveEventHomeBloc>(context)
        //            .add( FetchLiveEventHome() );
        //            //.add( FetchEvents( eventPage: nextUrlLiveEvents ) );
    }
    if (_scrollController.offset <= _scrollController.position.minScrollExtent &&
        !_scrollController.position.outOfRange) {
        print("reach the top");
    }
  }
  ///
  ///
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: BlocConsumer<LiveEventBloc, LiveEventState>(
        listener: (context, state){
          if (state is LiveEventLoaded) {
            _refreshCompleter?.complete();
            _refreshCompleter = Completer();
          }
        },
        builder:(context, state){
          //print("LiveEventBloc state is=$state");
          if (state is LiveEventLoaded || state is LiveEventNoAnimationLoading ) {
            // per aggiungere nell'array nuovi dati
            if (state is LiveEventLoaded ){
              final liveEventResponse = state.liveEventResponse;
              //print("feedResponse inside FeedLoaded");
              liveEvents.addAll(liveEventResponse.results);
              // setto il nuovo url!
              nextUrlLiveEvents = liveEventResponse.next;
              ///
            }
            return BlocBuilder<ThemeBloc, ThemeState>(
               builder: (context, themeState) {
                 return Column(
                   children: <Widget>[
                     OrganismEventHeader(scheduledDays: scheduledDays),
                     Expanded(
                       child: ListView.builder(
                         controller: _scrollController,
                         itemCount: liveEvents.length,
                         itemBuilder: (context, index) {
                           final liveEvent = liveEvents[index];
                           return OrganismEvent(liveEvent: liveEvent,);
                         },
                       ),
                     ),
                   ],
                 );
               }
             );
          }
          if (state is LiveEventLoading) {
            return Center(child: CircularProgressIndicator());
          }
          if (state is LiveEventEmpty) {
            // capire come sostituire il feed empty dopo il primo login!
            return Center(child: CircularProgressIndicator());
          }
          if (state is LiveEventError) {
            return Center(
               child: AtomText.errorType(
                 'Something went wrong!',
               )
             );
          }
          return Center(child: AtomText.errorType('No state events'));
        }
      )
    );
  }
}

@hackerunet
Copy link

I'm not a guru or high level flutter programmer, but seems to me, that you are overloading the initial loading of your blocs, and I repeat, I'm not senior or anything, but, in theory, you shouldn't have to call your providers on initState, you would have to create a MultiBlocProvider with your 3 blocs all blocs will be shared to the widgets tree, so in the View widgets you call your blocs through the context using BlocProvider.of(context), or something like that. so when blocs are passed to each child they are all initiated with the initialState definition and inside the bloc you should call in the initialState any other function you need.
So, I just say, I'm too junior to be 100% percent sure, but as far as I know, maybe i am wrong, the strategy is the one I just formulated. And of course, you still need to access to ProviderBuilder and ProviderListeners inside your widgets.
If I'm wrong, please let me know. but for me, seems that you are overloading the application on the wrong moment or the wrong way.

@hansbak
Copy link

hansbak commented Jun 23, 2020

To make the example work with flutter_bloc ^4.0.0

replace the top few lines with:

void main() {
 runApp(
    BlocProvider<MyBloc>(
      create: (context) => MyBloc(),
      child: MyApp(),
    ),
  );
}

@thaihuynhxyz
Copy link

thaihuynhxyz commented Sep 4, 2020

Hi @felangel I have a question if I have '/pageC' route as well as MyEvent.eventCand StateC. How about this scenario:

  1. In PageA I listen StateC to navigate to PageC.
  2. From PageA I navigate to PageB.
  3. Then somehow I trigger MyEvent.eventC from PageB.
    Is this normal behavior when PageA is inactive but still able to do navigation? Or Should we unregister the listener when the screen becomes inactive?

I update my code here:
https://gist.github.com/thaihuynhxyz/bf7b280f3e068b32c84189b4dedbc5a4

@tidharnitzan
Copy link

@felangel - thank you for this code.
we are struggling with a scenario close the code you have here but beside listener we also have a builder since our page have more state (e.g. we like to show a loader on loading state and so on).
we also have BL we must run, hence, going to the bloc class and yielding a state back to the listener to catch (and do navigation).
however our problem is that a new state means also the builder is being called and our logic there doesn't handle the state we dedicated for the navigation part.

How can I say - hey builder - this new state was meant for the listener only and I don't need a UI rebuild ?

@j-vasil
Copy link

j-vasil commented May 7, 2021

@felangel: Thank you for posting this "recipe!" It was extremely helpful in understanding how I need to proceed with the app I am currently developing (unlike so many "sample" apps that contain so much code that it's hard to extract the details you need for a specific technique). I found that diffing the two implementations (Direct vs Routes) helped me understand the two approaches. In case it would help others, here is the result: Comparison of Direct Navigation and Route Navigation using BLOC.

p.s. I'm open to any suggestions on better ways to preserve this comparison since I may want to use my github webpage for something else one day (now that I've learned about this feature :-)). I'd be happy to send copies of the html file or a pdf version (neither of which formats are supported here).

@j-vasil
Copy link

j-vasil commented May 7, 2021

With flutter_bloc ^7.0.0, the original "route" example has three errors (red ⓧ) and three warnings? (blue ⓘ).

A few posts above, @hansbak shows fixes for two of them:

To make the example work with flutter_bloc ^4.0.0
replace the top few lines with:

The code below shows no problems in VSCode. I doubt I'd have figured out the fixes for the first two without Hans' post so many thanks, @hansbak!!

import 'package:flutter/material.dart';
import 'package:meta/meta.dart';
import 'package:bloc/bloc.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

void main() {
  runApp(
    BlocProvider<MyBloc>(
      create: (context) => MyBloc(StateA()),
      child: MyApp(),
    ),
  );
}

enum MyEvent { eventA, eventB }

@immutable
abstract class MyState {}

class StateA extends MyState {}

class StateB extends MyState {}

class MyBloc extends Bloc<MyEvent, MyState> {

  MyBloc(MyState initialState) : super(initialState);

  @override
  Stream<MyState> mapEventToState(MyEvent event) async* {
    switch (event) {
      case MyEvent.eventA:
        yield StateA();
        break;
      case MyEvent.eventB:
        yield StateB();
        break;
    }
  }
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      routes: {
        '/': (context) => PageA(),
        '/pageB': (context) => PageB(),
      },
      initialRoute: '/',
    );
  }
}

class PageA extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocListener<MyBloc, MyState>(
      listener: (context, state) {
        if (state is StateB) {
          Navigator.of(context).pushNamed('/pageB');
        }
      },
      child: Scaffold(
        appBar: AppBar(
          title: Text('Page A'),
        ),
        body: Center(
          child: ElevatedButton(
            child: Text('Go to PageB'),
            onPressed: () {
              BlocProvider.of<MyBloc>(context).add(MyEvent.eventB);
            },
          ),
        ),
      ),
    );
  }
}

class PageB extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Page B'),
      ),
      body: Center(
        child: ElevatedButton(
          child: Text('Pop'),
          onPressed: () {
            Navigator.of(context).pop();
          },
        ),
      ),
    );
  }
}

@eliudio
Copy link

eliudio commented Nov 13, 2021

This is related to what I posted here: felangel/bloc#2938

I tried the example https://booiljung.github.io/technical_articles/flutter/bloc/recipes_flutter_navigation.html

(also the example from top of this page)

I've taken that example but rather than have "pop" I've implemented the Go to Page B. This is to allow to simulate a more complex application with navigation implemented in this way. Basically you want to be able to navigate from page to page to page to page. And when I do, this doesn't work as I would hope.

The code is copied below. I've added some logging as well. From the logging you can see the same unexpected / incorrect behaviour: going to pageB works fine, going back to pageA works fine as well, but then we get this funny behaviour where all the listeners of the widgets in the history gets triggered as well... I think. Basically, the output logfile is:

I/flutter ( 6717): Navigator.of(context).pushNamed('/pageB');
I/flutter ( 6717): Navigator.of(context).pushNamed('/pageA');
I/flutter ( 6717): Navigator.of(context).pushNamed('/pageB');
I/flutter ( 6717): Navigator.of(context).pushNamed('/pageB');
I/flutter ( 6717): Navigator.of(context).pushNamed('/pageA');
I/flutter ( 6717): Navigator.of(context).pushNamed('/pageA');
I/flutter ( 6717): Navigator.of(context).pushNamed('/pageA');
I/flutter ( 6717): Navigator.of(context).pushNamed('/pageB');
I/flutter ( 6717): Navigator.of(context).pushNamed('/pageB');
I/flutter ( 6717): Navigator.of(context).pushNamed('/pageB');
I/flutter ( 6717): Navigator.of(context).pushNamed('/pageB');
I/flutter ( 6717): Navigator.of(context).pushNamed('/pageB');
I/flutter ( 6717): Navigator.of(context).pushNamed('/pageA');
I/flutter ( 6717): Navigator.of(context).pushNamed('/pageA');
I/flutter ( 6717): Navigator.of(context).pushNamed('/pageA');
I/flutter ( 6717): Navigator.of(context).pushNamed('/pageA');
I/flutter ( 6717): Navigator.of(context).pushNamed('/pageA');
I/flutter ( 6717): Navigator.of(context).pushNamed('/pageA');
I/flutter ( 6717): Navigator.of(context).pushNamed('/pageA');
I/flutter ( 6717): Navigator.of(context).pushNamed('/pageA');

The output should be:

I/flutter ( 6717): Navigator.of(context).pushNamed('/pageB');
I/flutter ( 6717): Navigator.of(context).pushNamed('/pageA');
I/flutter ( 6717): Navigator.of(context).pushNamed('/pageB');
I/flutter ( 6717): Navigator.of(context).pushNamed('/pageA');
I/flutter ( 6717): Navigator.of(context).pushNamed('/pageB');
I/flutter ( 6717): Navigator.of(context).pushNamed('/pageB');
I/flutter ( 6717): Navigator.of(context).pushNamed('/pageA');

The code:

import 'package:flutter/material.dart';
import 'package:meta/meta.dart';
import 'package:bloc/bloc.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

void main() {
  runApp(
    BlocProvider(
      create: (context) => MyBloc(),
      child: MyApp(),
    ),
  );
}

enum MyEvent { eventA, eventB }

@immutable
abstract class MyState {}

class StateA extends MyState {}

class StateB extends MyState {}

class MyBloc extends Bloc<MyEvent, MyState> {
  MyBloc() : super(StateA());

  @override
  Stream<MyState> mapEventToState(MyEvent event) async* {
    switch (event) {
      case MyEvent.eventA:
        yield StateA();
        break;
      case MyEvent.eventB:
        yield StateB();
        break;
    }
  }
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      routes: {
        '/': (context) => PageA(),
        '/pageB': (context) => PageB(),
        '/pageA': (context) => PageA(),
      },
      initialRoute: '/',
    );
  }
}

class PageA extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocListener<MyBloc, MyState>(
      listener: (context, state) {
        if (state is StateB) {
          print("Navigator.of(context).pushNamed('/pageB');");
          Navigator.of(context).pushNamed('/pageB');
        }
      },
      child: Scaffold(
        appBar: AppBar(
          title: Text('Page A'),
        ),
        body: Center(
          child: RaisedButton(
            child: Text('Go to PageB'),
            onPressed: () {
              BlocProvider.of<MyBloc>(context).add(MyEvent.eventB);
            },
          ),
        ),
      ),
    );
  }
}

class PageB extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocListener<MyBloc, MyState>(
      listener: (context, state) {
        if (state is StateA) {
          print("Navigator.of(context).pushNamed('/pageA');");
          Navigator.of(context).pushNamed('/pageA');
        }
      },
      child: Scaffold(
        appBar: AppBar(
          title: Text('Page B'),
        ),
        body: Center(
          child: RaisedButton(
            child: Text('Go to PageA'),
            onPressed: () {
              BlocProvider.of<MyBloc>(context).add(MyEvent.eventA);
            },
          ),
        ),
      ),
    );
  }
}

@Ahmed-Omar-Hommir
Copy link

Same Problem
<<>>

@Iri-Hor
Copy link

Iri-Hor commented Dec 30, 2023

I updated the code to work with flutter_bloc 8.1.3

Because Flutter discourages the usage of named routes, I used the Navigator with anonymous routes.

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

void main() {
  runApp(
    BlocProvider(
      create: (context) => MyBloc(),
      child: const MyApp(),
    ),
  );
}

@immutable
sealed class MyEvent {}

final class EventA extends MyEvent {}

final class EventB extends MyEvent {}

@immutable
abstract class MyState {}

class StateA extends MyState {}

class StateB extends MyState {}

class MyBloc extends Bloc<MyEvent, MyState> {
  MyBloc() : super(StateA()) {
    on<EventA>((event, emit) => emit(StateA()));
    on<EventB>((event, emit) => emit(StateB()));
  }
}

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

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

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

  @override
  Widget build(BuildContext context) {
    return BlocListener<MyBloc, MyState>(
      listener: (context, state) {
        if (state is StateB) {
          Navigator.push(
            context,
            MaterialPageRoute(builder: (context) => const PageB()),
          );
        }
      },
      child: Scaffold(
        appBar: AppBar(
          title: const Text('Page A'),
        ),
        body: Center(
          child: ElevatedButton(
            child: const Text('Go to PageB'),
            onPressed: () {
              BlocProvider.of<MyBloc>(context).add(EventB());
            },
          ),
        ),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Page B'),
      ),
      body: Center(
        child: ElevatedButton(
          child: const Text('Pop'),
          onPressed: () {
            Navigator.of(context).pop();
          },
        ),
      ),
    );
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment