在 StreamBuilder 中使用 SnackBar 有什么奇特的方法?
What is the fancy way to use SnackBar in StreamBuilder?
我正在为我的应用程序实现 Bloc 模式,我必须显示 SnackBar,它会在登录未经身份验证时显示错误消息。
但是我无法在小部件的构建阶段显示 SnackBar。我找了很多解决方案,但我找不到。
使用此功能最有效的方法是什么?
我的代码
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(),
);
}
}
堆栈跟踪
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))
首先,您必须确保始终返回小部件
然后您可以安排 SnackBar
帧结束
if(state is AuthUnauthenticated){
WidgetsBinding.instance.addPostFrameCallback((_) => _showErrorMessage(state.errorMessage));
return Container();
}
您还应该检查 data
是否为空或 snapshot
是否有数据。
另一种 bloc 模式是从 BuildContext
中检索 Bloc
而不是将其保存在状态中。在这种情况下,您可以使用 BlocListener
而不是 StreamBuilder
.
class _SnackbarListener extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocListener< AuthBloc, AuthState>(
listener: (context, state) {
if(state is AuthUnauthenticated){
_showErrorMessage(state.errorMessage));
}
},
child: Container(),
);
}
}
我正在为我的应用程序实现 Bloc 模式,我必须显示 SnackBar,它会在登录未经身份验证时显示错误消息。
但是我无法在小部件的构建阶段显示 SnackBar。我找了很多解决方案,但我找不到。
使用此功能最有效的方法是什么?
我的代码
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(),
);
}
}
堆栈跟踪
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))
首先,您必须确保始终返回小部件
然后您可以安排 SnackBar
帧结束
if(state is AuthUnauthenticated){
WidgetsBinding.instance.addPostFrameCallback((_) => _showErrorMessage(state.errorMessage));
return Container();
}
您还应该检查 data
是否为空或 snapshot
是否有数据。
另一种 bloc 模式是从 BuildContext
中检索 Bloc
而不是将其保存在状态中。在这种情况下,您可以使用 BlocListener
而不是 StreamBuilder
.
class _SnackbarListener extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocListener< AuthBloc, AuthState>(
listener: (context, state) {
if(state is AuthUnauthenticated){
_showErrorMessage(state.errorMessage));
}
},
child: Container(),
);
}
}