Error when I try to show a SnackBar in a builder

2019-07-11 05:35发布

问题:

This is my main.dart:

class MyApp extends StatelessWidget {
var login = new Login("xxxxxxx", "xxxxxxxxx");
final scaffoldKey = new GlobalKey<ScaffoldState>();

@override
Widget build(BuildContext context) {
  return MaterialApp(
    title: 'Fetch Data Example',
    theme: ThemeData(
      primarySwatch: Colors.blue,
    ),
    home: Scaffold(
      key: scaffoldKey,
      appBar: AppBar(
        title: Text('Fetch Data Example'),
      ),
      body: Center(
        child: FutureBuilder(
          future: login.main(),
          builder: (context, snapshot) {
            if (snapshot.hasData) {
              _showSnackBar(snapshot.data);
              return new Container();
            } else if (snapshot.hasError) {
              return Text("${snapshot.error}");
            }
            // By default, show a loading spinner
            return CircularProgressIndicator();
          },
        ),
      ),
    ),
  );
}

_showSnackBar(var text) {
  scaffoldKey.currentState
      .showSnackBar(new SnackBar(content: new Text(text)));
}
}

I'd like to show a SnackBar in FutureBuilder, but when I run, something wrong occurs:

I/flutter ( 8918): ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY 
╞═══════════════════════════════════════════════════════════
I/flutter ( 8918): The following assertion was thrown building 
FutureBuilder<dynamic>(dirty, state:
I/flutter ( 8918): _FutureBuilderState<dynamic>#9d4af):
I/flutter ( 8918): setState() or markNeedsBuild() called during build.
I/flutter ( 8918): This Scaffold widget cannot be marked as needing to 
build because the framework is already in the
I/flutter ( 8918): process of building widgets. A widget can be marked 
as needing to be built during the build phase
I/flutter ( 8918): only if one of its ancestors is currently building. 
This exception is allowed because the framework
I/flutter ( 8918): builds parent widgets before children, which means 
a dirty descendant will always be built.
I/flutter ( 8918): Otherwise, the framework might not visit this 
widget during this build phase.
I/flutter ( 8918): The widget on which setState() or markNeedsBuild() 
was called was:
I/flutter ( 8918):   Scaffold-[LabeledGlobalKey<ScaffoldState>#e4a3f] 
(state: ScaffoldState#cf908(tickers: tracking 2
I/flutter ( 8918):   tickers))
I/flutter ( 8918): The widget which was currently being built when the 
offending call was made was:
I/flutter ( 8918):   FutureBuilder<dynamic>(dirty, state: _ 
FutureBuilderState<dynamic>#9d4af)
I/flutter ( 8918): 
I/flutter ( 8918): When the exception was thrown, this was the stack:
I/flutter ( 8918): #0      Element.markNeedsBuild.<anonymous closure> 
(package:flutter/src/widgets/framework.dart:3472:11)
I/flutter ( 8918): #1      Element.markNeedsBuild 
(package:flutter/src/widgets/framework.dart:3498:6)
I/flutter ( 8918): #2      State.setState 
(package:flutter/src/widgets/framework.dart:1141:14)
I/flutter ( 8918): #3      ScaffoldState.showSnackBar 
(package:flutter/src/material/scaffold.dart:1110:5)
I/flutter ( 8918): #4      MyApp._showSnackBar 
I/flutter ( 8918): #5      MyApp.build.<anonymous closure> 
I/flutter ( 8918): #6      _FutureBuilderState.build 
(package:flutter/src/widgets/async.dart)
I/flutter ( 8918): #7      StatefulElement.build 
(package:flutter/src/widgets/framework.dart:3766:27)
I/flutter ( 8918): #8      ComponentElement.performRebuild 
(package:flutter/src/widgets/framework.dart:3678:15)
I/flutter ( 8918): #9      Element.rebuild 
(package:flutter/src/widgets/framework.dart:3531:5)
I/flutter ( 8918): #10     BuildOwner.buildScope 
(package:flutter/src/widgets/framework.dart:2273:33)
I/flutter ( 8918): #11     

My purpose is that it shows a loading spinner before it gets the data, and once it got the data, it shows a SnackBar. So what should I do to fix the problem? Login.main is a function that GET data from the database.

回答1:

FutureBuilder or StreamBuilder should only used to build widgets, not to execute logic like navigation. Use instead initState like

@override
void initState() {
  super.initState();
  _checkLogin();
}

Future<void> _checkLogin() async {
  try {
    var data = await login.main();
    setState(() {
      _waitForLogin = false;
    });
    _showSnackBar(data);
  } catch(e) {
    setState(() {
      _waitForLogin = false;
      _loginErrorMessage = '$e';
    });
  }
}

bool _isWaitForLogin = true;
String _loginErrorMessage;

@override
Widget build(BuildContext context) {
  return MaterialApp(
    title: 'Fetch Data Example',
    theme: ThemeData(
      primarySwatch: Colors.blue,
    ),
    home: Scaffold(
      key: scaffoldKey,
      appBar: AppBar(
        title: Text('Fetch Data Example'),
      ),
      body: Center(
        child: _isWaitForLogin ? CircularProgressIndicator() : 
          (_loginErrorMessage != null ? Text(_loginErrorMessage : Container())
      ),
    ),
  );
}


回答2:

Could you try like this: modify _showSnackBar and pass the context

_showSnackBar(var text, BuildContext context) {
  ScaffoldKey.of(context)
  .showSnackBar(new SnackBar(content: new Text(text)));
}