Flutter BLoC: Navigator.pop in StreamBuilder in build() method
Asked Answered
S

3

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?

Scad answered 13/4, 2019 at 12:49 Comment(2)
You already mentioned the perfect solution.Friar
Possible duplicate of Navigating to a new screen when stream value in BLOC changesMarjoriemarjory
J
18

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

Judsen answered 20/5, 2019 at 11:47 Comment(1)
can we use it in BlocBuilder also?Aberdeen
U
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();
          }
        },
      ),
    ),
  );
} ```
Uptodate answered 23/2, 2021 at 18:29 Comment(4)
please can you explain this code? thanks!Injection
Every change in StreamBuilder are trigger twice. Navigator.pop can't be call twice.Uptodate
@GoldenLion it's not my question. So I can't give you that. It's just the bloc pattern like github.com/felangel/bloc/tree/master/examples/…Uptodate
I used implements and created a subclass for the different state response by blocIntranuclear
V
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)

Virginium answered 13/4, 2019 at 14:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.