felangel / bloc

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

BlocBuilder => builder not triggered after state change #119

Closed fvisticot closed 5 years ago

fvisticot commented 5 years ago

flutter_bloc: ^0.7.0

I have adapted your login sample to my app.

when i try to logout, the bloc is correctly triggered and change the state to AuthenticationUnauthenticated

BUT the BlocBuilder => builder method is not triggered ... and the login page is not displayed

flutter: Transition { currentState: AuthenticationAuthenticated, event: LoggedOut, nextState: AuthenticationLoading }
flutter: Transition { currentState: AuthenticationLoading, event: LoggedOut, nextState: AuthenticationUnauthenticated }
@override
  Widget build(BuildContext context) {
    return BlocProvider<AuthenticationBloc>(
      bloc: _authenticationBloc,
      child: MaterialApp(
        theme: ThemeData(
          brightness: Brightness.dark,
          primaryColor: ThemeData.dark().canvasColor,
          accentColor: Colors.white,
        ),
        routes: routes,
        home: _buildRootPage(),
      ),
    );
  }
Widget _buildRootPage() {
    return BlocBuilder<AuthenticationEvent, AuthenticationState>(
      bloc: _authenticationBloc,
      builder: (BuildContext context, AuthenticationState state) {
        print('State changed');

        if (state is AuthenticationUninitialized) {
          return Scaffold(
              body: Center(
            child: Text("init"),
          ));
        }

        if (state is AuthenticationLoading) {
          return Scaffold(
              body: Center(
            child: Text("loading"),
          ));
        }

in the settings page to logout:

void _logout() {
    AuthenticationBloc authenticationBloc =
        BlocProvider.of<AuthenticationBloc>(context);
    authenticationBloc.dispatch(LoggedOut());
  }
felangel commented 5 years ago

@fvisticot can you share the full code? How is the settings page displayed? Is it pushed onto the navigation stack? If so you'll need to pop it off when you dispatch the LoggedOut event.

fvisticot commented 5 years ago

My application is using the navigation stack You are right, if I set this code it is OK and I can see the login page. Is it what you said ?

BUT the builder is not called with the new state .... :( it is normal ?

void _logout() {
    AuthenticationBloc authenticationBloc =
        BlocProvider.of<AuthenticationBloc>(context);
    authenticationBloc.dispatch(LoggedOut());

    ApplicationSecureStorage().clear();
    Navigator.of(context)
        .pushNamedAndRemoveUntil('/login', (Route<dynamic> route) => false);
  }
felangel commented 5 years ago

@fvisticot it's because you are using pushNamedAndRemove which is removing the BlocBuilder. I would recommend you use Navigator.pop() instead.

felangel commented 5 years ago

Closing this for now. Please let me know if you're still having trouble 👍

fvisticot commented 5 years ago

Sorry to disturb you again but it is not clear for me.

The blocBuilder is created in a statefull Widget initialized in the main->runApp function.

I do not understand why the builder(context, state) method is not called when logout occurs (with or without pop function)...

Why the navigation stack can modify the mechanism ?

felangel commented 5 years ago

@fvisticot no problem! Can you please share the full code?

I'm guessing when you push the settings page you are removing the root page which has the BlocBuilder. Can you confirm that you are always just using Navigator.push and Navigator.pop instead of pushReplacement or pushNamedAndRemove? Those will destroy the navigation stack below and dispose the widget that had BlocBuilder.

fvisticot commented 5 years ago

You are the boss !!!! Thanx the lot... I was using a pushReplacement :( ... changing to push solve the pb !! Congratulation for your framework !!

matevarga commented 5 years ago

@felangel hey, would you be able to provide some pointers to any doc/tutorial that describes how to use navigation (e.g. with a drawer) alongside the bloc package? I find it quite hard to understand what's going on here -- I've tried just using push (nothing like pushRemove and pushReplacement) to display a sub-page, e.g.

                ListTile(
                  title: Text("Account"),
                  trailing: Icon(Icons.account_circle),
                  onTap: () {
                    Navigator.of(context).pop();
                    Navigator.of(context).push(MaterialPageRoute(
                        builder: (BuildContext context) => AccountScreen()));
                  },

but on the AccountScreen

return RaisedButton(
      onPressed: () {
        BlocProvider.of<AuthenticationBloc>(context).dispatch(
          LoggedOut(),
        );
      },
      child: Text('Sign Out'),

.. this doesn't work (the BlocProvider in the original tree (that's the parent of the drawer) doesn't get the callback). Maybe I could get it working by trial-and-error but it'd be better to understand what's going on. Thanks.

What I ended up doing is just calling

   Navigator.of(context)
            .pushReplacement(MaterialPageRoute(builder: (context) => App()));

after pressing logout, which looks a bit nasty (as this should be done through events, not imperatively).

Edit:

ah-ha, so based on your commit elsewhere https://github.com/boukmi/flutter_login/pull/1/commits/520885891b5cde3f93e5aab02ebe450a1094f2c3 .. I now pop the accountScreen before dispatching the LoggedOut() event, and everything works. Good, though I need to wrap my head around how to make this elegant. :)

felangel commented 5 years ago

@matevarga in flutter_bloc v0.11.0 we introduced BlocListener which is the recommended way to handle navigation or other "one-time actions" in response to state changes.

Check out the SnackBar Recipe for an example of how to show a SnackBar in response to a state change. Navigation should be handled in the same way in order to keep the builder function a pure function with no side effects.

matevarga commented 5 years ago

Thanks @felangel I'll take a look.

nerder commented 5 years ago

BlocListener which is the recommended way to handle navigation or other "one-time actions" in response to state changes.

So you mean that with this, i can use pushReplacementNamed?

I think there are legit use cases for it, for instance in my app I have a page that i want to show at the first login only in order to let the user select one first initial setup and then just go back to the normal flow of the app, using pushReplacementNamed i don't have the go back button in my AppBar.

How can i do accomplish with flutter_bloc?

EDIT: even pushNamed can cause the issue? because i change my code like that but it's still not working as expected

felangel commented 5 years ago

@nerder have you checked out the navigation recipe?

I believe using BlocListener should work just fine for your use case. Do you mind sharing a gist or link to a sample app? Thanks!

basnetjiten commented 5 years ago

@felangel can you please check this question once. https://stackoverflow.com/questions/57343887/flutter-navigator-pop-keeps-the-dialog-box-partially-visible-with-black-shadow

imdangle commented 4 years ago

Hi @felangel ! First, Thank you for the awesome package and great support to the Community. I've facing same issues. I used the Navigation Stack as you describe above. I've observed the Transition, the state changes correctly but BlocBuider doesn't rebuild. My app has 2 pages: Home and Filter and uses 1 Bloc. Everything works fine on Home page but when I push to Filter page and trigger event then pop back to Home page, state and data return correctly but BlocBuilder not change. Can you check my repos: https://github.com/lehaidangdev/football_players_app Thanks Instance of 'PlayerListingBloc' - FilterEvent Instance of 'PlayerListingBloc' - Transition { currentState: PlayerListingInitialState, event: FilterEvent, nextState: PlayerListingFetchingState } Request: _/api/players/s?minOverall=85&maxOverall=90&position= status: 200 Instance of 'PlayerListingBloc' - Transition { currentState: PlayerListingFetchingState, event: FilterEvent, nextState: PlayerListingFetchedState }

felangel commented 4 years ago

Hi @lehaidangdev 👋 Thanks for the positive feedback! I took a look at your app and I believe the problem is you're creating multiple instances of the PlayerListingBloc. There is one being created by BlocProvider in home.dart which looks good but then in the initState of filter.dart you are creating another instance so when you add events to the _playerBloc from filter.dart it is a different bloc instance than the one on home which is why home's BlocBuilder does not update.

Hope that helps 👍

imdangle commented 4 years ago

Thanks @felangel for suggestions. 😁

arbazadam commented 2 years ago

Hello @Felix, i am facing a similar problem where i am firing an event, which goes to the bloc but doesnt emit any state. I found this when i logged the on change using Bloc Observer. Below is where i am firing the event. Firing-event

This is my bloc. bloc

The above print statements can be seen on the console, but there is no state emitted. Any help will be appreciated.

MaximRyabovol commented 9 months ago

Had the same. In my case was problem with using BlocProvider.value instead of BlocProvider(create: ...).

screen B -pop()-> screen A (should use BlocProvider(create: BlocA()) but was used BlocProvider.value(value: BlocA()))