这是我在
auth_screen.dart
文件中的卡片小部件中使用的表格的一部分:
child: Obx(() => Form(
key: _formKey,
child: SingleChildScrollView(
child: Column(
children: <Widget>[
TextFormField(
decoration: const InputDecoration(labelText: 'E-Mail'),
keyboardType: TextInputType.emailAddress,
validator: (value) {
if (value!.isEmpty || !value.contains('@')) {
return 'Invalid email!';
}
},
onSaved: (value) {
_authData['email'] = value as String;
},
),
TextFormField(
decoration: const InputDecoration(labelText: 'Password'),
obscureText: true,
controller: _passwordController,
validator: (value) {
if (value!.isEmpty || value.length < 5) {
return 'Password is too short!';
}
},
onSaved: (value) {
_authData['password'] = value as String;
},
),
有两个
TextFormField
用于电子邮件和密码。
还有相关的
auth_controller.dart
文件如下:
enum AuthMode { Signup, Login }
class AuthController extends GetxController
with GetSingleTickerProviderStateMixin {
static AuthController instance = Get.find();
Rx<dynamic>? authMode = AuthMode.Login.obs;
RxBool? isLoading = false.obs;
String? _token;
DateTime? _expiryDate;
String? _userId;
Timer? _authTimer;
final _isAuth = false.obs;
AnimationController? controller;
Animation<Offset>? slideAnimation;
Animation<double>? opacityAnimation;
late TextEditingController passwordController;
final key = GlobalKey<FormState>();
@override
void onInit() {
super.onInit();
tryAutoLogin();
controller = AnimationController(
vsync: this,
duration: const Duration(
milliseconds: 300,
),
);
slideAnimation = Tween<Offset>(
begin: const Offset(0, -1.5),
end: const Offset(0, 0),
).animate(
CurvedAnimation(
parent: controller as Animation<double>,
curve: Curves.fastOutSlowIn,
),
);
opacityAnimation = Tween(begin: 0.0, end: 1.0).animate(
CurvedAnimation(
parent: controller as Animation<double>,
curve: Curves.easeIn,
),
);
// _heightAnimation.addListener(() => setState(() {}));
passwordController = TextEditingController();
}
@override
void onClose() {
super.onClose();
passwordController.dispose();
}
bool get isAuth {
_isAuth.value = token != null;
return _isAuth.value;
}
String? get token {
if (_expiryDate != null &&
_expiryDate!.isAfter(DateTime.now()) &&
_token != null) {
return _token;
}
return null;
}
String? get userId {
return _userId;
}
Future<void> _authenticate(
String email, String password, String urlSegment) async {
// print('app is here!!!5555');
// const host = "localhost";
final host = UniversalPlatform.isAndroid ? '10.0.2.2' : '127.0.0.1';
final url = Uri.parse('http://$host:8000/api/$urlSegment');
try {
final http.Response response = await http.post(
url,
headers: {"Content-Type": "application/json"},
body: json.encode(
{
'email': email,
'password': password,
//'returnSecureToken': true,
},
),
);
// print('this is responsde ' );
// print(response);
final responseData = json.decode(response.body);
print(responseData);
if (responseData['error'] != null) {
throw HttpException(responseData['error']['message']);
} else {
_token = responseData['idToken'];
_userId = responseData['id'];
_expiryDate = DateTime.now().add(
Duration(
milliseconds: responseData['expiresIn'],
),
);
}
_autoLogout();
// update();
final prefs = await SharedPreferences.getInstance();
final userData = json.encode(
{
'token': _token,
'userId': _userId,
'expiryDate': _expiryDate!.toIso8601String(),
},
);
prefs.setString('userData', userData);
isLoading?.value = false;
// print(prefs.getString('userData'));
Get.toNamed(rootRoute);
} catch (error) {
throw error;
}
}
Future<void> signup(String email, String password) async {
return _authenticate(email, password, 'signup');
}
Future<void> login(String email, String password) async {
return _authenticate(email, password, 'sessions');
}
Future<bool> tryAutoLogin() async {
final prefs = await SharedPreferences.getInstance();
if (!prefs.containsKey('userData')) {
return false;
}
final Map<String, Object> extractedUserData = Map<String, Object>.from(
json.decode(prefs.getString('userData') as String));
final expiryDate =
DateTime.parse(extractedUserData['expiryDate'] as String);
if (expiryDate.isBefore(DateTime.now())) {
return false;
}
_token = extractedUserData['token'] as String;
_userId = extractedUserData['userId'] as String;
_expiryDate = expiryDate;
_isAuth.value = true;
_autoLogout();
return true;
}
Future<void> logout() async {
_token = null;
_userId = null;
_expiryDate = null;
if (_authTimer != null) {
_authTimer!.cancel();
_authTimer = null;
}
// update();
final prefs = await SharedPreferences.getInstance();
// prefs.remove('userData');
prefs.clear();
_isAuth.value = false;
}
void _autoLogout() {
if (_authTimer != null) {
_authTimer!.cancel();
}
final timeToExpiry = _expiryDate!.difference(DateTime.now()).inSeconds;
_authTimer = Timer(Duration(seconds: timeToExpiry), logout);
}
}
当我启动应用程序时它似乎运行没有错误但是当我点击
TextFormField
s输入电子邮件或密码时,Android模拟器上的虚拟键盘立即打开和关闭并且不允许我输入任何内容.它还在 DEBUG CONSOLE 中显示以下消息:
D/InputConnectionAdaptor(3218):输入法切换光标 监控
就我而言,解决方案是将 GlobalKey() 从 build () 方法中移除
关闭键盘:
Form(key: GlobalKey<FormState>());
解决方案:
final _formKey = GlobalKey<FormState>();
Form(key: _formKey)
经过这么多的努力,我发现代替
StateLessWidget
请使用StatefullWidget
。
希望对你有帮助!
在
key
上设置“TextFormField
”属性为我解决了这个问题。我在 initState
方法中创建了密钥(尽管它可能在其他地方创建),使用 GlobalKey<FormFieldState>()
构造函数。
我遇到了同样的问题。解决方案是使用 Stateful Widget 并将 _formkey 直接放在类范围下,而不是在 Widget 构建下。
class _SignupFormState extends State<SignupForm> {
final _formkey = GlobalKey<FormState>();
@override
Widget build(BuildContext context) {
//rest of the code
}