Bloc: BlocBuilder builder function not called after pushReplacementNamed

Created on 28 Jan 2019  路  8Comments  路  Source: felangel/bloc

Describe the bug
Using the Login tutorial, after navigate to another page using pushReplacementNamed, the _AppState BlocBuilder builder function was not called when I dispatch a new state from the new Page.

To Reproduce
Steps to reproduce the behavior:

  1. Create a new Page and put a button to dispatch authenticationBloc.dispatch(LoggedOut()); using the global AuthenticationBloc;
  2. From Home or Login, navigate to new page using pushReplacementNamed;
  3. Press the button in the new page;
  4. See that the transition was called but the BlocBuilder builder function in the _AppState was not;

Expected behavior
From new page dispatch LoggedOut and change the MaterialApp home to LoadingIndicator and LoginPage after;

Additional context
I'm using the last version ^0.5.3 and testing in iOS simulator.

question

Most helpful comment

You can't use pushReplacement because, like I said, it will replace everything you currently had rendered with your new Route so then the changes in AuthenticationState will have no impact.

To achieve what you want you can modify the above code to:

import 'package:flutter/material.dart';

import 'package:flutter_bloc/flutter_bloc.dart';

import 'package:flutter_login/authentication/authentication.dart';

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Home'),
        actions: <Widget>[
          IconButton(
            icon: Icon(Icons.navigate_next),
            onPressed: () {
              Navigator.of(context).push(MaterialPageRoute(
                builder: (context) => OtherPage(),
              ));
            },
          )
        ],
      ),
      body: Container(
        child: Center(
          child: Text('Home'),
        ),
      ),
    );
  }
}

class OtherPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final AuthenticationBloc authenticationBloc =
        BlocProvider.of<AuthenticationBloc>(context);

    return Scaffold(
      appBar: AppBar(
        leading: Container(),
        title: Text('Other Widget'),
      ),
      body: Container(
        child: Center(
          child: RaisedButton(
            child: Text('logout'),
            onPressed: () {
              Navigator.popUntil(context, (ModalRoute.withName('/')));
              authenticationBloc.dispatch(LoggedOut());
            },
          ),
        ),
      ),
    );
  }
}

You can hide the back button by passing Container() as the leading in the AppBar.

Does that answer your question?

All 8 comments

This is because you are pushing a replacement so you are replacing everything with your new page and there is no _RootPage widget to be rebuilt.

I believe you鈥檇 get the expected behavior by just pushing and then calling pop before dispatching LoggedOut.

Does that help?

So, how do I avoid this? I already have put whole MaterialApp in the builder function but it doesn't work.

I have used the listen function to avoid BlocBuilder but without success.

I'm not sure I understand exactly what you want but you can do something like:

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Home'),
        actions: <Widget>[
          IconButton(
            icon: Icon(Icons.navigate_next),
            onPressed: () {
              Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (_) => OtherPage(),
                ),
              );
            },
          )
        ],
      ),
      body: Container(
        child: Center(
          child: Text('Home'),
        ),
      ),
    );
  }
}

class OtherPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final AuthenticationBloc authenticationBloc =
        BlocProvider.of<AuthenticationBloc>(context);

    return Scaffold(
      appBar: AppBar(
        title: Text('Other Widget'),
      ),
      body: Container(
        child: Center(
          child: RaisedButton(
            child: Text('logout'),
            onPressed: () {
              Navigator.popUntil(context, (ModalRoute.withName('/')));
              authenticationBloc.dispatch(LoggedOut());
            },
          ),
        ),
      ),
    );
  }
}

Using your example, I want avoid the use to press the back button and go back to HomePage. Your example just pop when the use press the button. I want remove the back button from app bar too.

I can achive all things using pushReplacement

You can't use pushReplacement because, like I said, it will replace everything you currently had rendered with your new Route so then the changes in AuthenticationState will have no impact.

To achieve what you want you can modify the above code to:

import 'package:flutter/material.dart';

import 'package:flutter_bloc/flutter_bloc.dart';

import 'package:flutter_login/authentication/authentication.dart';

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Home'),
        actions: <Widget>[
          IconButton(
            icon: Icon(Icons.navigate_next),
            onPressed: () {
              Navigator.of(context).push(MaterialPageRoute(
                builder: (context) => OtherPage(),
              ));
            },
          )
        ],
      ),
      body: Container(
        child: Center(
          child: Text('Home'),
        ),
      ),
    );
  }
}

class OtherPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final AuthenticationBloc authenticationBloc =
        BlocProvider.of<AuthenticationBloc>(context);

    return Scaffold(
      appBar: AppBar(
        leading: Container(),
        title: Text('Other Widget'),
      ),
      body: Container(
        child: Center(
          child: RaisedButton(
            child: Text('logout'),
            onPressed: () {
              Navigator.popUntil(context, (ModalRoute.withName('/')));
              authenticationBloc.dispatch(LoggedOut());
            },
          ),
        ),
      ),
    );
  }
}

You can hide the back button by passing Container() as the leading in the AppBar.

Does that answer your question?

Closing this for now but feel free to comment and I鈥檒l reopen it.

Navigator.popUntil(context, (ModalRoute.withName('/'))); did it for me, thanks!

Here's a solution that worked for me to hide the back button and prevent moving back conditionally.

WillPopScope(
      onWillPop: () async => false,
      child: Scaffold(
        appBar: AppBar(
          automaticallyImplyLeading: false,
        )))
Was this page helpful?
0 / 5 - 0 ratings