What is the fancy way to use SnackBar in StreamBuilder?

2,157

First, you must ensure that you are always returning a widget

and then you can schedule the SnackBar for the end of the frame

if(state is AuthUnauthenticated){
  WidgetsBinding.instance.addPostFrameCallback((_) => _showErrorMessage(state.errorMessage));
  return Container();
}

You should also check if the data is null o the snapshot has data.

Share:
2,157
baeharam
Author by

baeharam

Updated on December 08, 2022

Comments

  • baeharam
    baeharam over 1 year

    I'm implementing Bloc pattern for my application and I have to show SnackBar which shows error message when login is unauthenticated.

    But I cannot show SnackBar during building phase of widget. I looked for lots of solutions, but I couldn't found.

    What is the most efficient way to use this function?

    My code

    import 'package:chat_app/auth/auth_bloc.dart';
    import 'package:chat_app/auth/auth_state.dart';
    import 'package:chat_app/main_page.dart';
    import 'package:flutter/material.dart';
    
    void main() => runApp(App());
    
    class App extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Chat App',
          home: MyApp(),
          debugShowCheckedModeBanner: false,
        );
      }
    }
    
    class MyApp extends StatefulWidget {
      @override
      _MyAppState createState() => _MyAppState();
    }
    
    class _MyAppState extends State<MyApp> {
    
      final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
      final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
      final AuthBloc _bloc = AuthBloc();
    
      final _emailController = TextEditingController();
      final _passwordController = TextEditingController();
    
      @override
        void dispose() {
          _emailController.dispose();
          _passwordController.dispose();
          _bloc.dispose();
          super.dispose();
        }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          key: _scaffoldKey,
          appBar: AppBar(title: Text('Chat Example')),
          body: StreamBuilder(
              initialData: AuthInitializing(),
              stream: _bloc.authStream,
              builder: (BuildContext context, AsyncSnapshot<AuthState> snapshot){
                AuthState state = snapshot.data;
                if(state is AuthUnauthenticated){
                  _showErrorMessage(state.errorMessage);
                }
                if(state is AuthAuthenticated){
                  _moveNextPage(context);
                }
                return Form(
                  key: _formKey,
                  child: Padding(
                    padding: const EdgeInsets.symmetric(horizontal: 20.0),
                    child: Column(
                      children: <Widget>[
                        TextFormField(
                          controller: _emailController,
                          keyboardType: TextInputType.emailAddress,
                          decoration: InputDecoration(
                            border: OutlineInputBorder(),
                            labelText: '이메일',
                          ),
                        ),
                        SizedBox(height: 20.0),
                        TextFormField(
                          controller: _passwordController,
                          keyboardType: TextInputType.text,
                          obscureText: true,
                          decoration: InputDecoration(
                            border: OutlineInputBorder(),
                            labelText: '비밀번호'
                          ),
                        ),
                        SizedBox(height: 20.0),
                        RaisedButton(
                          child: Text('로그인',style: TextStyle(color: Colors.white),),
                          onPressed: () => _bloc.addLoginData(_emailController.text, _passwordController.text),
                          color: Theme.of(context).primaryColor,
                        ),
                        SizedBox(height: 15.0),
                        state is AuthLoading ? _progressBar() : Container()
                      ],
                    ),
                  ),
                );
              },
            ),
        );
      }
    
      void _showErrorMessage(String message){
        _scaffoldKey.currentState.showSnackBar(SnackBar(
          content: Text(message),
        ));
      }
    
      void _moveNextPage(BuildContext context) {
        Navigator.pushReplacement(context, MaterialPageRoute(
          builder: (_) => MainPage()
        ));
      }
    
      Widget _progressBar() {
        return Center(
          child: CircularProgressIndicator(),
        );
      }
    }
    

    StackTrace

    I/flutter (30505): ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════ I/flutter (30505): The following assertion was thrown building StreamBuilder(dirty, state: I/flutter (30505): _StreamBuilderBaseState>#bd8b2): I/flutter (30505): setState() or markNeedsBuild() called during build. I/flutter (30505): This Scaffold widget cannot be marked as needing to build because the framework is already in the I/flutter (30505): process of building widgets. A widget can be marked as needing to be built during the build phase I/flutter (30505): only if one of its ancestors is currently building. This exception is allowed because the framework I/flutter (30505): builds parent widgets before children, which means a dirty descendant will always be built. I/flutter (30505): Otherwise, the framework might not visit this widget during this build phase. I/flutter (30505): The widget on which setState() or markNeedsBuild() was called was: I/flutter (30505):
    Scaffold-[LabeledGlobalKey#5bdc5](state: ScaffoldState#61be4(tickers: tracking 2 I/flutter (30505): tickers))

  • baeharam
    baeharam over 5 years
    Great answer! Thanks