14

I'm following BLoC pattern and subscribing to stream, and reacting to state changes in build method. When data is loaded I want to close the screen.

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Bloc'),
      ),
      body: SafeArea(
        child: StreamBuilder<UserState>(
          stream: _userBloc.user,
          initialData: UserInitState(),
          builder: (context, snapshot) {
            if (snapshot.data is UserInitState) {
              return _buildInit();
            }
            if (snapshot.data is UserDataState) {
              Navigator.pop(context, true);
              return Container();
            }
            if (snapshot.data is UserLoadingState) {
              return _buildLoading();
            }
          },
        ),
      ),
    );
  } 

When I do Navigator.pop(context, true); in build() method I get:

I/flutter ( 4360): ══╡ EXCEPTION CAUGHT BY ANIMATION LIBRARY ╞═════════════════════════════════════════════════════════
I/flutter ( 4360): The following assertion was thrown while notifying status listeners for AnimationController:
I/flutter ( 4360): setState() or markNeedsBuild() called during build.
I/flutter ( 4360): This Overlay widget cannot be marked as needing to build because the framework is already in the
I/flutter ( 4360): process of building widgets. A widget can be marked as needing to be built during the build phase
I/flutter ( 4360): only if one of its ancestors is currently building. This exception is allowed because the framework
I/flutter ( 4360): builds parent widgets before children, which means a dirty descendant will always be built.
I/flutter ( 4360): Otherwise, the framework might not visit this widget during this build phase.

What is the right way to handle such cases in BLoC pattern?

On of the solutions I come up with is to start listening to stream on initState(). In this case I need to broadcast() my stream because I have 2 subscribers.

Are there any better solutions for this?

creativecreatorormaybenot
  • 114,516
  • 58
  • 291
  • 402
Vadims Savjolovs
  • 2,618
  • 1
  • 26
  • 51

3 Answers3

17

I think I have got a solution for you. (Please check it)

Make your code look like :

Widget build(BuildContext context) {
    // other stuff

    if (snapshot.data is UserDataState) {
      myCallback(() {
        Navigator.pop(context, true);
      });
    }

    // other stuff
}
// after build method (but still in the same class,...) write below method

void myCallback(Function callback) {
    WidgetsBinding.instance.addPostFrameCallback((_) {
      callback();
    });
}

Hope it helps. Just try it and please report here to help others too!

Source (flutter_bloc Login medium article)

Description

Ravi Kavaiya
  • 205
  • 2
  • 10
1
bool hasPop = false;

  @override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: const Text('Bloc'),
    ),
    body: SafeArea(
      child: StreamBuilder<UserState>(
        stream: _userBloc.user,
        initialData: UserInitState(),
        builder: (context, snapshot) {
          if (snapshot.data is UserInitState) {
            return _buildInit();
          }
          if (snapshot.data is UserDataState) {
            if(!hasPop){
               hasPop = true;
               Navigator.pop(context, true);
             }
            
            return Container();
          }
          if (snapshot.data is UserLoadingState) {
            return _buildLoading();
          }
        },
      ),
    ),
  );
} ```
mario francois
  • 1,291
  • 1
  • 9
  • 16
0

I could imagine three possible solutions:

1) It looks to me like it would be best to restructure your widgets. As far as I can see you want a "Loading Screen" .. I see no reason that this has to be it's own navigation item, instead of just another widget.

Ie. You could push the StreamBuilder one? level up.. so your builder method looks like:

if (!snapshot.hasData) {
  return LoadingScreen(snapshot);
}
// Whatever you do with your data.

2) I think i personally would create a StatefulWidget, listen to the stream manually in initState() and call setState() manually. No need for a StreamBuilder

3) as a crazy workaround you could probably use Future(() { Navigator.of(context).pop(); }); in your builder. (it's possible that you'd have to use the context of the build method, not the builder. but I wouldn't recommend that solution anyway)

Herbert Poul
  • 4,512
  • 2
  • 31
  • 48