使用 Futter/Dart/BLoC 第二次按下后登录按钮不会重置状态
Login button will not reset state after second press with Futter/Dart/BLoC
我一直在尝试使用 Flutter/Dart 和 BLoC 构建登录页面。到目前为止,我有一个登录页面,其中包含两个用户名和密码输入字段以及一个由 rounded_loading_button.dart.
构建的登录按钮
在任何一个字段都没有任何内容的情况下首次按下登录按钮时,信息栏中会返回相应的错误,并且按钮会被重置。
但是,我遇到的问题是,如果您再次按下该按钮,该按钮将继续旋转,并且小吃栏再也不会显示。就好像没有以某种方式返回任何状态。
我已经检查了代码,尝试重置状态,但我没有找到解决方案。我在下面提供了我正在使用的所有文件。
在这种情况下,设置 LoginUserState.failed 似乎只起作用一次,我似乎无法弄清楚如何重置它,所以第二次尝试将再次显示小吃栏和任何错误,并重置登录按钮.
Main.dart 文件
import 'package:auto_orientation/auto_orientation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:go_router/go_router.dart';
import 'package:/screens/login.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
AutoOrientation.portraitAutoMode();
runApp(TestLoginApp());
}
class TestLoginApp extends StatelessWidget {
TestLoginApp({Key? key}) : super(key: key);
final _router = GoRouter(
routes: [
GoRoute(
name: 'login',
path: '/login',
builder: (context, state) => LoginPage(),
),
],
);
// This widget is the root of your application.
@override
Widget build(BuildContext context) => MaterialApp.router(
routeInformationParser: _router.routeInformationParser,
routerDelegate: _router.routerDelegate,
title: 'Login test with BLoC',
theme: ThemeData(
backgroundColor: Colors.black,
),
);
}
Login.dart 文件
import 'package:auto_orientation/auto_orientation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:rounded_loading_button/rounded_loading_button.dart';
import 'package:/services/api.dart';
import '../cubit/login_user_cubit.dart';
import '../singleton.dart';
class LoginPage extends StatefulWidget {
LoginPage({Key, key, this.title}) : super(key: key);
final String? title;
@override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => LoginUserCubit(LoginUserState.initial),
child: LoginWidget(),
);
}
}
class LoginWidget extends StatefulWidget {
@override
_LoginWidgetState createState() => _LoginWidgetState();
}
class _LoginWidgetState extends State<LoginWidget> {
TextStyle style = const TextStyle(fontSize: 15.0);
final TextEditingController _emailController = TextEditingController();
final TextEditingController _passwordController = TextEditingController();
final RoundedLoadingButtonController _btnController =
RoundedLoadingButtonController();
final _scaffoldKey = GlobalKey<ScaffoldState>();
final _singleton = Singleton();
bool _waiting = false;
bool _buttonEnable = false;
String loginError = "";
@override
void initState() {
super.initState();
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
]);
}
@override
Widget build(BuildContext context) {
List<Widget> _buildPage(BuildContext context) {
final loginButton = RoundedLoadingButton(
controller: _btnController,
color: Theme.of(context).primaryColor,
onPressed: () => {
setState(() {
_waiting = true;
}),
context.read<LoginUserCubit>().loginUser(
email: _emailController.text,
password: _passwordController.text,
env: _singleton.env)
},
child: const Text(
"Login now",
textAlign: TextAlign.center,
style: TextStyle(color: Colors.white),
));
final emailField = TextField(
controller: _emailController,
obscureText: false,
autocorrect: false,
enableSuggestions: false,
autofocus: true,
keyboardType: TextInputType.emailAddress,
style: const TextStyle(fontSize: 16),
decoration: InputDecoration(
filled: true,
hintText: "Email",
fillColor:
_singleton.error != "" ? const Color(0xFFFFD5D5) : Colors.white,
contentPadding: const EdgeInsets.fromLTRB(20.0, 15.0, 20.0, 15.0),
focusedBorder: const OutlineInputBorder(
borderSide: BorderSide(color: Colors.white),
borderRadius: BorderRadius.all(Radius.circular(0))),
enabledBorder: const OutlineInputBorder(
borderSide: BorderSide(color: Colors.white),
borderRadius: BorderRadius.all(Radius.circular(0))),
),
onChanged: (e) {
setState(() {
_singleton.error = "";
_buttonEnable = e.isNotEmpty;
});
});
final passwordField = TextField(
controller: _passwordController,
obscureText: true,
style: const TextStyle(fontSize: 16),
decoration: InputDecoration(
filled: true,
hintText: "Password",
fillColor:
_singleton.error != "" ? const Color(0xFFFFD5D5) : Colors.white,
contentPadding: const EdgeInsets.fromLTRB(20.0, 15.0, 20.0, 15.0),
focusedBorder: const OutlineInputBorder(
borderSide: BorderSide(color: Colors.white),
borderRadius: BorderRadius.all(Radius.circular(0))),
enabledBorder: const OutlineInputBorder(
borderSide: BorderSide(color: Colors.white),
borderRadius: BorderRadius.all(Radius.circular(0))),
),
onChanged: (e) {
setState(() {
_buttonEnable = e.isNotEmpty;
});
});
List<Widget> eList = [];
eList.add(Text(loginError,
style: const TextStyle(fontSize: 10, color: Color(0xFFFFD5D5))));
var page = SingleChildScrollView(
child: Center(
child: Padding(
padding: const EdgeInsets.all(40.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const SizedBox(height: 55.0),
Image.asset(
"assets/logo.png",
width: 200,
),
const SizedBox(height: 44.0),
emailField,
const SizedBox(height: 25),
passwordField,
const SizedBox(height: 25),
Flexible(
child: Align(
alignment: FractionalOffset.bottomCenter,
child:
Column(children: [loginButton]))),
const SizedBox(height: 20),
],
),
),
),
);
var l = <Widget>[];
l.add(page);
if (_waiting) {
var modal = Stack(
children: const [
Opacity(
opacity: 0.3,
child: ModalBarrier(dismissible: false, color: Colors.grey),
)
],
);
l.add(modal);
}
return l;
}
return BlocConsumer<LoginUserCubit, LoginUserState>(
listener: (context, state) {
switch (state) {
case LoginUserState.success:
// We can redirect the user somewhere else
break;
case LoginUserState.failed:
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
backgroundColor: Colors.red,
content: Text('Login failed'),
),
);
_btnController.reset();
break;
case LoginUserState.initial:
_waiting = false;
_btnController.reset();
break;
}
}, builder: (context, state) {
print(state);
return Scaffold(
key: _scaffoldKey,
backgroundColor: Colors.black,
body: Stack(children: _buildPage(context)));
});
}
}
Api.dart 文件
import 'dart:async';
import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:/cubit/login_user_cubit.dart';
import 'package:/services/auth_registration_listener.dart';
import 'package:/functions/save_current_login.dart';
import 'package:/models/login_error.dart';
import 'package:/models/user.dart';
import '../singleton.dart';
const baseUrl = "https://localwebsite.com";
class TheAPI {
var api = Dio();
String? accessToken;
final _storage = const FlutterSecureStorage();
final _singleton = Singleton();
final env = "test";
TheAPI() {
api.interceptors
.add(InterceptorsWrapper(onRequest: (options, handler) async {
if (!options.path.contains('http')) {
options.path = baseUrl + options.path;
}
options.headers['Authorization'] = 'Bearer $accessToken';
return handler.next(options);
}, onError: (DioError error, handler) async {
if ((error.response?.statusCode == 401 &&
error.response?.data['message'] == "Invalid JWT")) {
if (await _storage.containsKey(key: 'refreshToken')) {
if (await refreshToken()) {
return handler.resolve(await _retry(error.requestOptions));
}
}
}
LoginUserState.failed;
return handler.next(error);
}));
}
Future<Response<dynamic>> _retry(RequestOptions requestOptions) async {
final options = Options(
method: requestOptions.method,
headers: requestOptions.headers,
);
return api.request<dynamic>(requestOptions.path,
data: requestOptions.data,
queryParameters: requestOptions.queryParameters,
options: options);
}
Future<LoginUserState> generateToken(
String email, String password, String env) async {
const uri = baseUrl + "/account/generateToken";
Map<String, String> body = {
'email': email,
'password': password,
};
final b = json.encode(body);
try {
final response = await api.post(uri, data: b);
final responseJson = response.data;
_singleton.token = User.fromJson(responseJson).token;
_singleton.refreshToken = User.fromJson(responseJson).refreshToken;
_singleton.email = email;
_singleton.passwd = password;
_singleton.env = env;
await clearCurrentLogin();
await saveCurrentLogin(email, password, env, "false", responseJson);
return LoginUserState.success;
} on DioError catch (e) {
if (e.response?.statusCode == 400 || e.response?.statusCode == 404) {
_singleton.error = "Invalid Email or Password";
return LoginUserState.failed;
}
return LoginUserState.failed;
}
}
Future<bool> refreshToken() async {
final refreshToken = await _storage.read(key: 'refreshToken');
final response = await api.post(baseUrl + '/api/account/refreshToken',
data: {'refreshToken': refreshToken});
if (response.statusCode == 201) {
accessToken = response.data;
return true;
} else {
// refresh token is wrong
accessToken = null;
_storage.deleteAll();
return false;
}
}
Future<bool> changePassword(
String email, String password, String repeatPassword) async {
const uri = baseUrl + "/api/account/resetPassword";
Map<String, String> body = {
'email': email,
'password': password,
'repeatPassword': repeatPassword,
};
final b = json.encode(body);
final response = await api.post(
uri,
data: b,
);
if (response.statusCode == 200) {
final responseJson = response.data;
_singleton.token = User.fromJson(responseJson).token;
_singleton.refreshToken = User.fromJson(responseJson).refreshToken;
await saveCurrentLogin(email, password, env, "false", responseJson);
return true;
}
return false;
}
}
Login_user_cubit.dart 文件
import 'package:bloc/bloc.dart';
import 'package:/services/api.dart';
import '../services/auth_registration_listener.dart';
enum LoginUserState { success, failed, initial }
class LoginUserCubit extends Cubit<LoginUserState> implements AuthListener {
LoginUserCubit(LoginUserState initialState) : super(initialState);
final _api = TheAPI();
void loginUser(
{required String email,
required String password,
required String env}) async =>
emit(await _api.generateToken(email, password, env));
@override
void failed() {
emit(LoginUserState.initial);
emit(LoginUserState.failed);
}
}
如果状态与之前的状态相同,Bloc 将不会重建其子状态,在您的情况下,您会产生相同的状态。
一个简单的解决方案是在
之前产生不同的状态
emit(await _api.generateToken(email, password, env));
查看此 link 了解更多详情
我一直在尝试使用 Flutter/Dart 和 BLoC 构建登录页面。到目前为止,我有一个登录页面,其中包含两个用户名和密码输入字段以及一个由 rounded_loading_button.dart.
构建的登录按钮在任何一个字段都没有任何内容的情况下首次按下登录按钮时,信息栏中会返回相应的错误,并且按钮会被重置。
但是,我遇到的问题是,如果您再次按下该按钮,该按钮将继续旋转,并且小吃栏再也不会显示。就好像没有以某种方式返回任何状态。
我已经检查了代码,尝试重置状态,但我没有找到解决方案。我在下面提供了我正在使用的所有文件。
在这种情况下,设置 LoginUserState.failed 似乎只起作用一次,我似乎无法弄清楚如何重置它,所以第二次尝试将再次显示小吃栏和任何错误,并重置登录按钮.
Main.dart 文件
import 'package:auto_orientation/auto_orientation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:go_router/go_router.dart';
import 'package:/screens/login.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
AutoOrientation.portraitAutoMode();
runApp(TestLoginApp());
}
class TestLoginApp extends StatelessWidget {
TestLoginApp({Key? key}) : super(key: key);
final _router = GoRouter(
routes: [
GoRoute(
name: 'login',
path: '/login',
builder: (context, state) => LoginPage(),
),
],
);
// This widget is the root of your application.
@override
Widget build(BuildContext context) => MaterialApp.router(
routeInformationParser: _router.routeInformationParser,
routerDelegate: _router.routerDelegate,
title: 'Login test with BLoC',
theme: ThemeData(
backgroundColor: Colors.black,
),
);
}
Login.dart 文件
import 'package:auto_orientation/auto_orientation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:rounded_loading_button/rounded_loading_button.dart';
import 'package:/services/api.dart';
import '../cubit/login_user_cubit.dart';
import '../singleton.dart';
class LoginPage extends StatefulWidget {
LoginPage({Key, key, this.title}) : super(key: key);
final String? title;
@override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => LoginUserCubit(LoginUserState.initial),
child: LoginWidget(),
);
}
}
class LoginWidget extends StatefulWidget {
@override
_LoginWidgetState createState() => _LoginWidgetState();
}
class _LoginWidgetState extends State<LoginWidget> {
TextStyle style = const TextStyle(fontSize: 15.0);
final TextEditingController _emailController = TextEditingController();
final TextEditingController _passwordController = TextEditingController();
final RoundedLoadingButtonController _btnController =
RoundedLoadingButtonController();
final _scaffoldKey = GlobalKey<ScaffoldState>();
final _singleton = Singleton();
bool _waiting = false;
bool _buttonEnable = false;
String loginError = "";
@override
void initState() {
super.initState();
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
]);
}
@override
Widget build(BuildContext context) {
List<Widget> _buildPage(BuildContext context) {
final loginButton = RoundedLoadingButton(
controller: _btnController,
color: Theme.of(context).primaryColor,
onPressed: () => {
setState(() {
_waiting = true;
}),
context.read<LoginUserCubit>().loginUser(
email: _emailController.text,
password: _passwordController.text,
env: _singleton.env)
},
child: const Text(
"Login now",
textAlign: TextAlign.center,
style: TextStyle(color: Colors.white),
));
final emailField = TextField(
controller: _emailController,
obscureText: false,
autocorrect: false,
enableSuggestions: false,
autofocus: true,
keyboardType: TextInputType.emailAddress,
style: const TextStyle(fontSize: 16),
decoration: InputDecoration(
filled: true,
hintText: "Email",
fillColor:
_singleton.error != "" ? const Color(0xFFFFD5D5) : Colors.white,
contentPadding: const EdgeInsets.fromLTRB(20.0, 15.0, 20.0, 15.0),
focusedBorder: const OutlineInputBorder(
borderSide: BorderSide(color: Colors.white),
borderRadius: BorderRadius.all(Radius.circular(0))),
enabledBorder: const OutlineInputBorder(
borderSide: BorderSide(color: Colors.white),
borderRadius: BorderRadius.all(Radius.circular(0))),
),
onChanged: (e) {
setState(() {
_singleton.error = "";
_buttonEnable = e.isNotEmpty;
});
});
final passwordField = TextField(
controller: _passwordController,
obscureText: true,
style: const TextStyle(fontSize: 16),
decoration: InputDecoration(
filled: true,
hintText: "Password",
fillColor:
_singleton.error != "" ? const Color(0xFFFFD5D5) : Colors.white,
contentPadding: const EdgeInsets.fromLTRB(20.0, 15.0, 20.0, 15.0),
focusedBorder: const OutlineInputBorder(
borderSide: BorderSide(color: Colors.white),
borderRadius: BorderRadius.all(Radius.circular(0))),
enabledBorder: const OutlineInputBorder(
borderSide: BorderSide(color: Colors.white),
borderRadius: BorderRadius.all(Radius.circular(0))),
),
onChanged: (e) {
setState(() {
_buttonEnable = e.isNotEmpty;
});
});
List<Widget> eList = [];
eList.add(Text(loginError,
style: const TextStyle(fontSize: 10, color: Color(0xFFFFD5D5))));
var page = SingleChildScrollView(
child: Center(
child: Padding(
padding: const EdgeInsets.all(40.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const SizedBox(height: 55.0),
Image.asset(
"assets/logo.png",
width: 200,
),
const SizedBox(height: 44.0),
emailField,
const SizedBox(height: 25),
passwordField,
const SizedBox(height: 25),
Flexible(
child: Align(
alignment: FractionalOffset.bottomCenter,
child:
Column(children: [loginButton]))),
const SizedBox(height: 20),
],
),
),
),
);
var l = <Widget>[];
l.add(page);
if (_waiting) {
var modal = Stack(
children: const [
Opacity(
opacity: 0.3,
child: ModalBarrier(dismissible: false, color: Colors.grey),
)
],
);
l.add(modal);
}
return l;
}
return BlocConsumer<LoginUserCubit, LoginUserState>(
listener: (context, state) {
switch (state) {
case LoginUserState.success:
// We can redirect the user somewhere else
break;
case LoginUserState.failed:
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
backgroundColor: Colors.red,
content: Text('Login failed'),
),
);
_btnController.reset();
break;
case LoginUserState.initial:
_waiting = false;
_btnController.reset();
break;
}
}, builder: (context, state) {
print(state);
return Scaffold(
key: _scaffoldKey,
backgroundColor: Colors.black,
body: Stack(children: _buildPage(context)));
});
}
}
Api.dart 文件
import 'dart:async';
import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:/cubit/login_user_cubit.dart';
import 'package:/services/auth_registration_listener.dart';
import 'package:/functions/save_current_login.dart';
import 'package:/models/login_error.dart';
import 'package:/models/user.dart';
import '../singleton.dart';
const baseUrl = "https://localwebsite.com";
class TheAPI {
var api = Dio();
String? accessToken;
final _storage = const FlutterSecureStorage();
final _singleton = Singleton();
final env = "test";
TheAPI() {
api.interceptors
.add(InterceptorsWrapper(onRequest: (options, handler) async {
if (!options.path.contains('http')) {
options.path = baseUrl + options.path;
}
options.headers['Authorization'] = 'Bearer $accessToken';
return handler.next(options);
}, onError: (DioError error, handler) async {
if ((error.response?.statusCode == 401 &&
error.response?.data['message'] == "Invalid JWT")) {
if (await _storage.containsKey(key: 'refreshToken')) {
if (await refreshToken()) {
return handler.resolve(await _retry(error.requestOptions));
}
}
}
LoginUserState.failed;
return handler.next(error);
}));
}
Future<Response<dynamic>> _retry(RequestOptions requestOptions) async {
final options = Options(
method: requestOptions.method,
headers: requestOptions.headers,
);
return api.request<dynamic>(requestOptions.path,
data: requestOptions.data,
queryParameters: requestOptions.queryParameters,
options: options);
}
Future<LoginUserState> generateToken(
String email, String password, String env) async {
const uri = baseUrl + "/account/generateToken";
Map<String, String> body = {
'email': email,
'password': password,
};
final b = json.encode(body);
try {
final response = await api.post(uri, data: b);
final responseJson = response.data;
_singleton.token = User.fromJson(responseJson).token;
_singleton.refreshToken = User.fromJson(responseJson).refreshToken;
_singleton.email = email;
_singleton.passwd = password;
_singleton.env = env;
await clearCurrentLogin();
await saveCurrentLogin(email, password, env, "false", responseJson);
return LoginUserState.success;
} on DioError catch (e) {
if (e.response?.statusCode == 400 || e.response?.statusCode == 404) {
_singleton.error = "Invalid Email or Password";
return LoginUserState.failed;
}
return LoginUserState.failed;
}
}
Future<bool> refreshToken() async {
final refreshToken = await _storage.read(key: 'refreshToken');
final response = await api.post(baseUrl + '/api/account/refreshToken',
data: {'refreshToken': refreshToken});
if (response.statusCode == 201) {
accessToken = response.data;
return true;
} else {
// refresh token is wrong
accessToken = null;
_storage.deleteAll();
return false;
}
}
Future<bool> changePassword(
String email, String password, String repeatPassword) async {
const uri = baseUrl + "/api/account/resetPassword";
Map<String, String> body = {
'email': email,
'password': password,
'repeatPassword': repeatPassword,
};
final b = json.encode(body);
final response = await api.post(
uri,
data: b,
);
if (response.statusCode == 200) {
final responseJson = response.data;
_singleton.token = User.fromJson(responseJson).token;
_singleton.refreshToken = User.fromJson(responseJson).refreshToken;
await saveCurrentLogin(email, password, env, "false", responseJson);
return true;
}
return false;
}
}
Login_user_cubit.dart 文件
import 'package:bloc/bloc.dart';
import 'package:/services/api.dart';
import '../services/auth_registration_listener.dart';
enum LoginUserState { success, failed, initial }
class LoginUserCubit extends Cubit<LoginUserState> implements AuthListener {
LoginUserCubit(LoginUserState initialState) : super(initialState);
final _api = TheAPI();
void loginUser(
{required String email,
required String password,
required String env}) async =>
emit(await _api.generateToken(email, password, env));
@override
void failed() {
emit(LoginUserState.initial);
emit(LoginUserState.failed);
}
}
如果状态与之前的状态相同,Bloc 将不会重建其子状态,在您的情况下,您会产生相同的状态。
一个简单的解决方案是在
之前产生不同的状态emit(await _api.generateToken(email, password, env));
查看此 link 了解更多详情