flutter - 当使用 flutter_bloc 库在 Flutter 中推送新路由时,上下文无法正常工作
问题描述
当我使用 signin_content.dart 中的表单登录时,我导航到主页。然后我可以注销,因为我没有推送到另一个屏幕,所以它工作得很好。
问题是当我启动应用程序时,我看到第一页是 signin_content.dart。
然后,如果我推送到 signup_content.dart 并返回到 signin_content.dart 并尝试登录,则提供程序中的上下文不起作用。
BlocProvider.of<AuthenticationBloc>(context).add(LoggedIn()),
我已经尝试过这个解决方案,但我需要看看如何找到代码:
我尝试将提供程序用作 MaterialApp 的父级,但如何?
我正在使用这个库:
主要.dart
// imports ..
class SimpleBlocDelegate extends BlocDelegate {
@override
void onEvent(Bloc bloc, Object event) {
super.onEvent(bloc, event);
print(event);
}
@override
void onTransition(Bloc bloc, Transition transition) {
super.onTransition(bloc, transition);
print(transition);
}
@override
void onError(Bloc bloc, Object error, StackTrace stacktrace) {
super.onError(bloc, error, stacktrace);
print(error);
}
}
void main() {
BlocSupervisor.delegate = SimpleBlocDelegate();
final userRepository = UserRepository();
runApp(
BlocProvider<AuthenticationBloc>(
create: (context) {
return AuthenticationBloc(userRepository: userRepository)
..add(AppStarted());
},
child: App(userRepository: userRepository),
),
);
}
应用程序.dart
// imports..
class App extends StatelessWidget {
final UserRepository userRepository;
const App({
Key key,
@required this.userRepository,
}) : assert(userRepository != null),
super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: fitnessTheme(),
routes: Routes.appRoutes,
home: BlocBuilder<AuthenticationBloc, AuthenticationState>(
builder: (context, state) {
if (state is AuthenticationLoading) {
return LoadingIndicator();
}
if (state is AuthenticationUnauthenticated) {
return SigninScreen();
}
if (state is AuthenticationProfileInactive) {
return WelcomeScreen();
}
if (state is AuthenticationAuthenticated) {
return HomeScreen();
}
return Splash();
},
),
);
}
}
signin_content.dart
//imports..
class SigninContent extends StatefulWidget {
SigninContent({Key key}) : super(key: key);
_SigninContentState createState() => _SigninContentState();
}
class _SigninContentState extends State<SigninContent> {
SigninFormBloc _signinFormBloc;
final TextEditingController _emailController = TextEditingController();
final TextEditingController _passwordController = TextEditingController();
final UserProvider _userProvider = UserProvider();
@override
void initState() {
super.initState();
_signinFormBloc = BlocProvider.of<SigninFormBloc>(context);
_emailController.addListener(_onEmailChanged);
_passwordController.addListener(_onPasswordChanged);
}
@override
Widget build(BuildContext context) {
final Size size = MediaQuery.of(context).size;
print(BlocProvider.of<AuthenticationBloc>(context).state);
return BlocBuilder<SigninFormBloc, SigninFormState>(
builder: (context, state) {
if (state.formSubmittedSuccessfully) {
final authData = {
'email': _emailController.value.text,
'password': _passwordController.value.text,
};
_userProvider.signin(
data: authData,
success: () =>
BlocProvider.of<AuthenticationBloc>(context).add(LoggedIn()),
error: (message) {
Scaffold.of(context).showSnackBar(
buildSnackbar(
message,
Colors.red[700],
Colors.white,
Duration(seconds: 2),
),
);
},
);
// _emailController.clear();
// _passwordController.clear();
_signinFormBloc.add(FormReset());
}
return Column(
children: <Widget>[
Expanded(
child: SingleChildScrollView(
child: Container(
margin: EdgeInsets.only(top: 16.0),
padding: EdgeInsets.only(
top: 29.0,
left: 14.0,
right: 14.0,
),
child: Column(
children: <Widget>[
textInputWidget(
controller: _emailController,
labelText: "Email",
hintText: 'Enter a valid email',
autovalidate: state.email.isEmpty ? false : true,
validator: (_) {
return state.isEmailValid ? null : 'Invalid Email';
},
),
SizedBox(height: 20.0),
textInputWidget(
controller: _passwordController,
labelText: "Password",
hintText: 'Enter a valid password',
obscureText: true,
autovalidate: state.password.isEmpty ? false : true,
validator: (_) {
return state.isPasswordValid
? null
: 'Invalid Password';
},
),
SizedBox(height: 20.0),
primaryButton(
caption: "sign in",
context: context,
submit: state.isFormValid ? _onSubmitPressed : null,
),
SizedBox(height: 10.0),
Row(
children: <Widget>[
Text("Need an account?"),
FlatButton(
materialTapTargetSize:
MaterialTapTargetSize.shrinkWrap,
textColor: Color(0xFF32AEE2),
child: Text(
"Sign up",
style: TextStyle(
fontSize: 14.0,
fontFamily: "SF Pro Text",
),
),
onPressed: () => Navigator.pushReplacementNamed(
context,
SignupScreen.routeName,
),
)
],
),
Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Text("Forgot your password?"),
FlatButton(
padding: EdgeInsets.all(0),
materialTapTargetSize:
MaterialTapTargetSize.shrinkWrap,
textColor: Color(0xFF32AEE2),
child: Text(
"Reset",
style: TextStyle(
fontSize: 14.0,
fontFamily: "SF Pro Text",
),
),
onPressed: () => Navigator.pushReplacementNamed(
context,
PasswordResetScreen.routeName,
),
)
],
),
),
],
),
),
),
),
],
);
},
);
}
@override
void dispose() {
_emailController.dispose();
_passwordController.dispose();
super.dispose();
}
void _onEmailChanged() {
_signinFormBloc.add(EmailChanged(email: _emailController.text));
}
void _onPasswordChanged() {
_signinFormBloc.add(PasswordChanged(password: _passwordController.text));
}
void _onSubmitPressed() {
_signinFormBloc.add(FormSubmitted());
}
}
注册内容.dart
// imports..
class SignupContent extends StatefulWidget {
SignupContent({Key key}) : super(key: key);
_SignupContentState createState() => _SignupContentState();
}
class _SignupContentState extends State<SignupContent> {
SignupFormBloc _signupFormBloc;
final TextEditingController _nameController = TextEditingController();
final TextEditingController _emailController = TextEditingController();
final TextEditingController _dobController = TextEditingController();
final TextEditingController _passwordController = TextEditingController();
final TextEditingController _passwordConfirmationController =
TextEditingController();
final UserProvider _userProvider = UserProvider();
@override
void initState() {
super.initState();
_signupFormBloc = BlocProvider.of<SignupFormBloc>(context);
_nameController.addListener(_onNameChanged);
_emailController.addListener(_onEmailChanged);
_dobController.addListener(_onDobChanged);
_passwordController.addListener(_onPasswordChanged);
_passwordConfirmationController.addListener(_onPasswordConfirmationChanged);
}
@override
Widget build(BuildContext context) {
final Size _size = MediaQuery.of(context).size;
print(BlocProvider.of<AuthenticationBloc>(context).state);
return BlocBuilder<SignupFormBloc, SignupFormState>(
builder: (context, state) {
if (state.formSubmittedSuccessfully) {
final userData = {
'name': _nameController.value.text,
'email': _emailController.value.text,
'password': _passwordController.value.text,
};
_userProvider.signup(
userData: userData,
success: () {
BlocProvider.of<AuthenticationBloc>(context).add(
SignedUp(
email: userData["email"],
password: userData["password"],
),
);
},
error: (message) {
Scaffold.of(context).showSnackBar(
buildSnackbar(
message,
Colors.red[700],
Colors.white,
Duration(seconds: 2),
),
);
},
);
_signupFormBloc.add(FormReset());
}
return Column(
children: <Widget>[
authHeader(_size),
Expanded(
child: SingleChildScrollView(
child: Container(
margin: EdgeInsets.only(top: 16.0),
padding: EdgeInsets.only(
top: 29.0,
left: 14.0,
right: 14.0,
),
child: Column(
children: <Widget>[
textInputWidget(
controller: _nameController,
labelText: "Name",
hintText: 'Enter a valid name',
autovalidate: state.name.isEmpty ? false : true,
validator: (_) {
return state.isNameValid
? null
: 'At least 6 characters long.';
},
),
SizedBox(height: 20.0),
textInputWidget(
controller: _emailController,
labelText: "Email",
hintText: 'Enter a valid email',
autovalidate: state.email.isEmpty ? false : true,
validator: (_) {
return state.isEmailValid ? null : 'Invalid Email';
},
),
SizedBox(height: 20.0),
textInputWidget(
controller: _passwordController,
labelText: "Password",
hintText: 'Enter a valid password',
obscureText: true,
autovalidate: state.password.isEmpty ? false : true,
validator: (_) {
return state.isPasswordValid
? null
: 'Invalid Password';
},
),
SizedBox(height: 20.0),
textInputWidget(
controller: _passwordConfirmationController,
labelText: "Password Confirmation",
hintText: 'Enter a valid password',
autovalidate:
state.passwordConfirmation.isEmpty ? false : true,
validator: (_) {
return state.isPasswordConfirmationValid
? null
: 'Invalid Password';
},
obscureText: true,
),
SizedBox(height: 20.0),
primaryButton(
caption: "sign up",
context: context,
submit: state.isFormValid ? _onSubmitPressed : null,
),
SizedBox(height: 10.0),
Row(
children: <Widget>[
Text("Already Registered?"),
FlatButton(
materialTapTargetSize:
MaterialTapTargetSize.shrinkWrap,
textColor: Color(0xFF32AEE2),
child: Text(
"Sign in",
style: TextStyle(
fontSize: 14.0,
fontFamily: "SF Pro Text",
),
),
onPressed: () => Navigator.pushReplacementNamed(
context,
SigninScreen.routeName,
),
)
],
),
],
),
),
),
),
],
);
},
);
}
@override
void dispose() {
_nameController.dispose();
_emailController.dispose();
_passwordController.dispose();
_passwordConfirmationController.dispose();
super.dispose();
}
void _onNameChanged() {
_signupFormBloc.add(NameChanged(name: _nameController.text));
}
void _onEmailChanged() {
_signupFormBloc.add(EmailChanged(email: _emailController.text));
}
void _onPasswordChanged() {
_signupFormBloc.add(PasswordChanged(password: _passwordController.text));
}
void _onPasswordConfirmationChanged() {
_signupFormBloc.add(
PasswordConfirmationChanged(
password: _passwordController.text,
passwordConfirmation: _passwordConfirmationController.text,
),
);
}
void _onSubmitPressed() {
_signupFormBloc.add(
FormSubmitted(),
);
}
}
解决方案
这是因为在SignupContent
你替换你的家MaterialApp
,所以BlocBuilder
即使身份验证状态改变,小部件也不会改变。
要修复它,我建议您删除BlocBuilder
并在每个屏幕中使用 a BlocListener
,这样您就可以Navigator
毫无问题地使用 。
所以改变你main
的
void main() {
BlocSupervisor.delegate = SimpleBlocDelegate();
final userRepository = UserRepository();
final authenticationBloc = AuthenticationBloc(userRepository: userRepository);
authenticationBloc
.firstWhere((state) =>
state is! AuthenticationUninitialized &&
state is! AuthenticationLoading)
.then((state) => runApp(App(
authenticationBloc: authenticationBloc,
home: state is AuthenticationUnauthenticated
? SigninScreen()
: HomeScreen(),
)));
authenticationBloc.add(AppStarted());
}
同时更改您的App
小部件
class App extends StatelessWidget {
const App({
Key key,
@required this.home,
@required this.authenticationBloc,
}) : super(key: key);
final Widget home;
final AuthenticationBloc authenticationBloc;
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: <BlocProvider>[
BlocProvider<AuthenticationBloc>.value(value: authenticationBloc),
],
child: MaterialApp(
debugShowCheckedModeBanner: false,
theme: fitnessTheme(),
routes: Routes.appRoutes,
home: home,
),
);
}
}
然后BlocListener
在您的SigninScreen
and中使用 aSignupScreen
进行导航,并在身份验证状态更改时显示加载对话框。
class SigninScreen extends StatelessWidget {
static const String routeName = "signin";
const SigninScreen({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
ScreenRatio.setScreenRatio(
MediaQuery.of(context).size.height,
MediaQuery.of(context).size.width,
);
return BlocProvider(
create: (context) => SigninFormBloc(),
child: BlocListener<AuthenticationBloc, AuthenticationState>(
listener: (context, state) {
if (state is AuthenticationLoading) {
LoadingDialog.show(context);
} else if (state is AuthenticationUnauthenticated) {
LoadingDialog.hide(context);
} else if (state is AuthenticationAuthenticated) {
LoadingDialog.hide(context);
Navigator.of(context).pushNamedAndRemoveUntil(
HomeScreen.routeName, (route) => false);
}
},
child: Scaffold(
body: SafeArea(
child: SigninContent(),
),
),
));
}
}
class SignupScreen extends StatelessWidget {
static const String routeName = "signup";
const SignupScreen({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
ScreenRatio.setScreenRatio(
MediaQuery.of(context).size.height, MediaQuery.of(context).size.width);
return BlocProvider(
create: (context) => SigninFormBloc(),
child: BlocListener<AuthenticationBloc, AuthenticationState>(
listener: (context, state) {
if (state is AuthenticationLoading) {
LoadingDialog.show(context);
} else if (state is AuthenticationUnauthenticated) {
LoadingDialog.hide(context);
} else if (state is AuthenticationAuthenticated) {
LoadingDialog.hide(context);
Navigator.of(context).pushNamedAndRemoveUntil(
HomeScreen.routeName, (route) => false);
}
},
child: Scaffold(
body: SafeArea(
child: SignupContent(),
),
),
));
}
}
class LoadingDialog extends StatelessWidget {
static void show(BuildContext context, {Key key}) {
showDialog<void>(
context: context,
barrierDismissible: false,
builder: (_) => LoadingDialog(key: key),
);
}
static void hide(BuildContext context) {
Navigator.pop(context);
}
LoadingDialog({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async => false,
child: Center(
child: Card(
child: Container(
width: 80,
height: 80,
padding: EdgeInsets.all(12.0),
child: CircularProgressIndicator(),
),
),
),
);
}
}
最后在用户退出时HomeScreen
使用 aBlocListener
进行导航
return BlocListener<AuthenticationBloc, AuthenticationState>(
listener: (context, state) {
if (state is AuthenticationUnauthenticated) {
Navigator.of(context).pushNamedAndRemoveUntil(
SigninScreen.routeName, (route) => false);
}
},
child: Scaffold(
body: Center(
child: RaisedButton(
onPressed: () {
BlocProvider.of<AuthenticationBloc>(context).add(LoggedOut());
},
child: Text("Sign out"),
),
),
),
);
推荐阅读
- python-3.x - 索引中的 boto3 dynamodb batch_get_item 不起作用
- python - 数据框获取索引为日期时间类型的两个行位置之间的值列表
- python - Python - 汇总每列分组总数中的行
- r - R:将ggplot2图保存在不同的excel表中
- java - 如何在 HTML 3.2 中的表格单元格 () 中将文本与图像 ()对齐
- kubernetes - 如何正确命名使用 Helm 部署的 Kubernetes 资源?
- html - 如何在Firefox中隐藏滚动条箭头
- python - scikit learn 是否提供了一种更简单的解决方案来获得系数的置信度?
- php - PHP echo 正在切断连接中的最后一个字符串
- java - 在 LinkedList 的末尾添加一个元素