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();
},
),
),
);
}
}
@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