在 flutter 中发布到 firestore 时如何验证文本字段?
How to validate textfield when posting to firestore in flutter?
我在 TextField 中添加了验证器,但在提交数据时验证器不工作。保存按钮以空值保存数据。
import 'package:cloud_firestore/cloud_firestore.dart';
import '../../screens/orders/order_success.dart';
import '../../services/global_methods.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
class PlaceOrder extends StatefulWidget {
static const routeName = '/place-order';
const PlaceOrder({Key? key}) : super(key: key);
@override
State<PlaceOrder> createState() => _PlaceOrderState();
}
class _PlaceOrderState extends State<PlaceOrder> {
final _formKey = GlobalKey<FormState>();
String? _name;
int? _phoneNumber;
String? _fullAddress;
String? _area;
String? _city;
String? _addressType;
final TextEditingController _addressController = TextEditingController();
String? _addressValue;
String? _deliverySlotValue;
final FirebaseAuth _auth = FirebaseAuth.instance;
late bool _isLoading = false;
final GlobalMethods _globalMethods = GlobalMethods();
final FocusNode _numberFocusNode = FocusNode();
@override
void initState() {
setState(() {
_isLoading = false;
});
super.initState();
}
@override
void dispose() {
_numberFocusNode.dispose();
super.dispose();
}
void _submitData() async {
final _isValid = _formKey.currentState!.validate();
FocusScope.of(context).unfocus();
if (_isValid) {
_formKey.currentState!.save();
}
try {
setState(() {
_isLoading = true;
});
final User? user = _auth.currentUser;
final _uid = user!.uid;
final orderId = ModalRoute.of(context)!.settings.arguments as String;
FirebaseFirestore.instance
.collection('orders')
.doc(orderId)
.set({
'userId': _uid,
'name': _name,
'phoneNumber': _phoneNumber,
'addressType': _addressType,
'address': _fullAddress,
'area': _area,
'city': _city,
'deliverySlot': _deliverySlotValue,
}, SetOptions(merge: true));
} catch (error) {
_globalMethods.authDialog(context, error.toString());
} finally {
setState(() {
_isLoading = false;
});
Navigator.of(context).pushReplacementNamed(OrderSuccess.routeName);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Add new address'),
),
body: Form(
key: _formKey,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Row(
children: [
DropdownButton<String>(
items: const [
DropdownMenuItem<String>(
child: Text('Home'),
value: 'Home',
),
DropdownMenuItem<String>(
child: Text('Office'),
value: 'Office',
),
DropdownMenuItem<String>(
child: Text('Other'),
value: 'Other',
),
],
onChanged: (value) {
setState(() {
_addressValue = value.toString();
_addressController.text = value.toString();
print(_addressType);
});
},
hint: const Text('Select address type'),
value: _addressValue,
),
const SizedBox(
width: 10,
),
Expanded(
child: TextFormField(
key: const ValueKey('addressType'),
controller: _addressController,
validator: (val) {
if (val!.isEmpty) {
return 'Please select address type';
}
return null;
},
decoration: const InputDecoration(
labelText: 'Select your address type',
),
onSaved: (val) {
_addressType = val.toString();
},
),
),
],
),
TextFormField(
onSaved: (value) {
_name = value!;
},
textInputAction: TextInputAction.next,
key: const ValueKey('name'),
validator: (value) {
if (value!.isEmpty) {
return 'Please enter your name';
}
return null;
},
decoration: InputDecoration(
labelText: 'Name',
filled: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
prefixIcon: const Icon(Icons.location_city),
),
),
const SizedBox(height: 8),
TextFormField(
onSaved: (value) {
_fullAddress = value!;
},
textInputAction: TextInputAction.next,
key: const ValueKey('streetAddress'),
validator: (value) {
if (value!.isEmpty) {
return 'Please enter your address';
}
return null;
},
decoration: InputDecoration(
labelText: 'Address',
filled: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
prefixIcon: const Icon(Icons.location_city),
),
),
const SizedBox(height: 8),
TextFormField(
onSaved: (value) {
_area = value!;
},
textInputAction: TextInputAction.next,
key: const ValueKey('area'),
validator: (value) {
if (value!.isEmpty) {
return 'Please enter your area';
}
return null;
},
decoration: InputDecoration(
labelText: 'Area',
filled: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
prefixIcon: const Icon(Icons.location_city),
),
),
const SizedBox(height: 8),
TextFormField(
onSaved: (value) {
_city = value!;
},
textInputAction: TextInputAction.next,
key: const ValueKey('city'),
validator: (value) {
if (value!.isEmpty) {
return 'Please enter your city';
}
return null;
},
decoration: InputDecoration(
labelText: 'City',
filled: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
prefixIcon: const Icon(Icons.location_city),
),
),
const SizedBox(height: 8),
TextFormField(
onSaved: (value) {
_phoneNumber = int.parse(value!);
},
textInputAction: TextInputAction.next,
// keyboardType: TextInputType.emailAddress,
onEditingComplete: () =>
FocusScope.of(context).requestFocus(_numberFocusNode),
key: const ValueKey('number'),
validator: (value) {
if (value!.length < 10) {
return 'Phone number must be 10 units';
}
return null;
},
decoration: InputDecoration(
labelText: 'Phone Number',
filled: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
prefixIcon: const Icon(Icons.phone),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
DropdownButton<String>(
items: const [
DropdownMenuItem<String>(
child: Text('11:00am to 1:00pm'),
value: '11:00am to 1:00pm',
),
DropdownMenuItem<String>(
child: Text('1:00pm to 3:00pm'),
value: '1:00pm to 3:00pm',
),
DropdownMenuItem<String>(
child: Text('3:00pm to 5:00pm'),
value: '3:00pm to 5:pm',
),
DropdownMenuItem<String>(
child: Text('5:00pm to 7:00pm'),
value: '5:00pm to 7:00pm',
),
DropdownMenuItem<String>(
child: Text('7:00pm to 9:pm'),
value: '7:00pm to 9:pm',
),
],
onChanged: (value) {
setState(() {
_deliverySlotValue = value.toString();
});
},
hint: const Text('Select Delivery Slot'),
value: _deliverySlotValue,
),
],
),
Padding(
padding: const EdgeInsets.all(48.0),
child: SizedBox(
child: ElevatedButton(
onPressed: _submitData,
child: _isLoading
? const CircularProgressIndicator()
: const Text(
'S U B M I T',
style: TextStyle(color: Colors.white),
),
),
),
),
],
),
),
),
),
);
}
}
唯一可能的原因是因为 _addressType
它的初始值是 null
因为它是一个 String?
变量所以如果用户没有 select 一个“交付时段”
保存后的结果是 null
,
你需要做什么,检查 _addressType
的值是否为 null
然后继续保存表单,因为你的表单不会检查它是否为空.
问题似乎是您在以下代码中的控制流的一个简单错误(请参阅 // 注释):
void _submitData() async {
final _isValid = _formKey.currentState!.validate();
FocusScope.of(context).unfocus();
if (_isValid) {
_formKey.currentState!.save();
} // <----- this should not be here!
try {
setState(() {
_isLoading = true;
});
final User? user = _auth.currentUser;
final _uid = user!.uid;
final orderId = ModalRoute.of(context)!.settings.arguments as String;
FirebaseFirestore.instance
.collection('orders')
.doc(orderId)
.set({
'userId': _uid,
'name': _name,
'phoneNumber': _phoneNumber,
'addressType': _addressType,
'address': _fullAddress,
'area': _area,
'city': _city,
'deliverySlot': _deliverySlotValue,
}, SetOptions(merge: true));
} catch (error) {
_globalMethods.authDialog(context, error.toString());
} finally {
setState(() {
_isLoading = false;
});
Navigator.of(context).pushReplacementNamed(OrderSuccess.routeName);
}
}
如您所见,即使用户输入无效,您仍然继续将结果上传到 Firebase。
尝试这样更正:
void _submitData() async {
final _isValid = _formKey.currentState!.validate();
FocusScope.of(context).unfocus();
if (_isValid) {
_formKey.currentState!.save();
try {
setState(() {
_isLoading = true;
});
final User? user = _auth.currentUser;
final _uid = user!.uid;
final orderId = ModalRoute.of(context)!.settings.arguments as String;
FirebaseFirestore.instance.collection('orders').doc(orderId).set({
'userId': _uid,
'name': _name,
'phoneNumber': _phoneNumber,
'addressType': _addressType,
'address': _fullAddress,
'area': _area,
'city': _city,
'deliverySlot': _deliverySlotValue,
}, SetOptions(merge: true));
} catch (error) {
_globalMethods.authDialog(context, error.toString());
} finally {
setState(() {
_isLoading = false;
});
Navigator.of(context).pushReplacementNamed(OrderSuccess.routeName);
}
} else {
// do nothing
}
}
您可以将表单验证器添加到提交按钮本身。当您单击提交按钮时,以下代码有效。
Padding(
padding: const EdgeInsets.all(48.0),
child: SizedBox(
child: ElevatedButton(
onPressed: (){
if (_formKey.currentState!.validate()){
_submitData;
}
},
child: _isLoading
? const CircularProgressIndicator()
: const Text(
'S U B M I T',
style: TextStyle(color: Colors.white),
),
),
),
),
我在 TextField 中添加了验证器,但在提交数据时验证器不工作。保存按钮以空值保存数据。
import 'package:cloud_firestore/cloud_firestore.dart';
import '../../screens/orders/order_success.dart';
import '../../services/global_methods.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
class PlaceOrder extends StatefulWidget {
static const routeName = '/place-order';
const PlaceOrder({Key? key}) : super(key: key);
@override
State<PlaceOrder> createState() => _PlaceOrderState();
}
class _PlaceOrderState extends State<PlaceOrder> {
final _formKey = GlobalKey<FormState>();
String? _name;
int? _phoneNumber;
String? _fullAddress;
String? _area;
String? _city;
String? _addressType;
final TextEditingController _addressController = TextEditingController();
String? _addressValue;
String? _deliverySlotValue;
final FirebaseAuth _auth = FirebaseAuth.instance;
late bool _isLoading = false;
final GlobalMethods _globalMethods = GlobalMethods();
final FocusNode _numberFocusNode = FocusNode();
@override
void initState() {
setState(() {
_isLoading = false;
});
super.initState();
}
@override
void dispose() {
_numberFocusNode.dispose();
super.dispose();
}
void _submitData() async {
final _isValid = _formKey.currentState!.validate();
FocusScope.of(context).unfocus();
if (_isValid) {
_formKey.currentState!.save();
}
try {
setState(() {
_isLoading = true;
});
final User? user = _auth.currentUser;
final _uid = user!.uid;
final orderId = ModalRoute.of(context)!.settings.arguments as String;
FirebaseFirestore.instance
.collection('orders')
.doc(orderId)
.set({
'userId': _uid,
'name': _name,
'phoneNumber': _phoneNumber,
'addressType': _addressType,
'address': _fullAddress,
'area': _area,
'city': _city,
'deliverySlot': _deliverySlotValue,
}, SetOptions(merge: true));
} catch (error) {
_globalMethods.authDialog(context, error.toString());
} finally {
setState(() {
_isLoading = false;
});
Navigator.of(context).pushReplacementNamed(OrderSuccess.routeName);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Add new address'),
),
body: Form(
key: _formKey,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Row(
children: [
DropdownButton<String>(
items: const [
DropdownMenuItem<String>(
child: Text('Home'),
value: 'Home',
),
DropdownMenuItem<String>(
child: Text('Office'),
value: 'Office',
),
DropdownMenuItem<String>(
child: Text('Other'),
value: 'Other',
),
],
onChanged: (value) {
setState(() {
_addressValue = value.toString();
_addressController.text = value.toString();
print(_addressType);
});
},
hint: const Text('Select address type'),
value: _addressValue,
),
const SizedBox(
width: 10,
),
Expanded(
child: TextFormField(
key: const ValueKey('addressType'),
controller: _addressController,
validator: (val) {
if (val!.isEmpty) {
return 'Please select address type';
}
return null;
},
decoration: const InputDecoration(
labelText: 'Select your address type',
),
onSaved: (val) {
_addressType = val.toString();
},
),
),
],
),
TextFormField(
onSaved: (value) {
_name = value!;
},
textInputAction: TextInputAction.next,
key: const ValueKey('name'),
validator: (value) {
if (value!.isEmpty) {
return 'Please enter your name';
}
return null;
},
decoration: InputDecoration(
labelText: 'Name',
filled: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
prefixIcon: const Icon(Icons.location_city),
),
),
const SizedBox(height: 8),
TextFormField(
onSaved: (value) {
_fullAddress = value!;
},
textInputAction: TextInputAction.next,
key: const ValueKey('streetAddress'),
validator: (value) {
if (value!.isEmpty) {
return 'Please enter your address';
}
return null;
},
decoration: InputDecoration(
labelText: 'Address',
filled: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
prefixIcon: const Icon(Icons.location_city),
),
),
const SizedBox(height: 8),
TextFormField(
onSaved: (value) {
_area = value!;
},
textInputAction: TextInputAction.next,
key: const ValueKey('area'),
validator: (value) {
if (value!.isEmpty) {
return 'Please enter your area';
}
return null;
},
decoration: InputDecoration(
labelText: 'Area',
filled: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
prefixIcon: const Icon(Icons.location_city),
),
),
const SizedBox(height: 8),
TextFormField(
onSaved: (value) {
_city = value!;
},
textInputAction: TextInputAction.next,
key: const ValueKey('city'),
validator: (value) {
if (value!.isEmpty) {
return 'Please enter your city';
}
return null;
},
decoration: InputDecoration(
labelText: 'City',
filled: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
prefixIcon: const Icon(Icons.location_city),
),
),
const SizedBox(height: 8),
TextFormField(
onSaved: (value) {
_phoneNumber = int.parse(value!);
},
textInputAction: TextInputAction.next,
// keyboardType: TextInputType.emailAddress,
onEditingComplete: () =>
FocusScope.of(context).requestFocus(_numberFocusNode),
key: const ValueKey('number'),
validator: (value) {
if (value!.length < 10) {
return 'Phone number must be 10 units';
}
return null;
},
decoration: InputDecoration(
labelText: 'Phone Number',
filled: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
prefixIcon: const Icon(Icons.phone),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
DropdownButton<String>(
items: const [
DropdownMenuItem<String>(
child: Text('11:00am to 1:00pm'),
value: '11:00am to 1:00pm',
),
DropdownMenuItem<String>(
child: Text('1:00pm to 3:00pm'),
value: '1:00pm to 3:00pm',
),
DropdownMenuItem<String>(
child: Text('3:00pm to 5:00pm'),
value: '3:00pm to 5:pm',
),
DropdownMenuItem<String>(
child: Text('5:00pm to 7:00pm'),
value: '5:00pm to 7:00pm',
),
DropdownMenuItem<String>(
child: Text('7:00pm to 9:pm'),
value: '7:00pm to 9:pm',
),
],
onChanged: (value) {
setState(() {
_deliverySlotValue = value.toString();
});
},
hint: const Text('Select Delivery Slot'),
value: _deliverySlotValue,
),
],
),
Padding(
padding: const EdgeInsets.all(48.0),
child: SizedBox(
child: ElevatedButton(
onPressed: _submitData,
child: _isLoading
? const CircularProgressIndicator()
: const Text(
'S U B M I T',
style: TextStyle(color: Colors.white),
),
),
),
),
],
),
),
),
),
);
}
}
唯一可能的原因是因为 _addressType
它的初始值是 null
因为它是一个 String?
变量所以如果用户没有 select 一个“交付时段”
保存后的结果是 null
,
你需要做什么,检查 _addressType
的值是否为 null
然后继续保存表单,因为你的表单不会检查它是否为空.
问题似乎是您在以下代码中的控制流的一个简单错误(请参阅 // 注释):
void _submitData() async {
final _isValid = _formKey.currentState!.validate();
FocusScope.of(context).unfocus();
if (_isValid) {
_formKey.currentState!.save();
} // <----- this should not be here!
try {
setState(() {
_isLoading = true;
});
final User? user = _auth.currentUser;
final _uid = user!.uid;
final orderId = ModalRoute.of(context)!.settings.arguments as String;
FirebaseFirestore.instance
.collection('orders')
.doc(orderId)
.set({
'userId': _uid,
'name': _name,
'phoneNumber': _phoneNumber,
'addressType': _addressType,
'address': _fullAddress,
'area': _area,
'city': _city,
'deliverySlot': _deliverySlotValue,
}, SetOptions(merge: true));
} catch (error) {
_globalMethods.authDialog(context, error.toString());
} finally {
setState(() {
_isLoading = false;
});
Navigator.of(context).pushReplacementNamed(OrderSuccess.routeName);
}
}
如您所见,即使用户输入无效,您仍然继续将结果上传到 Firebase。
尝试这样更正:
void _submitData() async {
final _isValid = _formKey.currentState!.validate();
FocusScope.of(context).unfocus();
if (_isValid) {
_formKey.currentState!.save();
try {
setState(() {
_isLoading = true;
});
final User? user = _auth.currentUser;
final _uid = user!.uid;
final orderId = ModalRoute.of(context)!.settings.arguments as String;
FirebaseFirestore.instance.collection('orders').doc(orderId).set({
'userId': _uid,
'name': _name,
'phoneNumber': _phoneNumber,
'addressType': _addressType,
'address': _fullAddress,
'area': _area,
'city': _city,
'deliverySlot': _deliverySlotValue,
}, SetOptions(merge: true));
} catch (error) {
_globalMethods.authDialog(context, error.toString());
} finally {
setState(() {
_isLoading = false;
});
Navigator.of(context).pushReplacementNamed(OrderSuccess.routeName);
}
} else {
// do nothing
}
}
您可以将表单验证器添加到提交按钮本身。当您单击提交按钮时,以下代码有效。
Padding(
padding: const EdgeInsets.all(48.0),
child: SizedBox(
child: ElevatedButton(
onPressed: (){
if (_formKey.currentState!.validate()){
_submitData;
}
},
child: _isLoading
? const CircularProgressIndicator()
: const Text(
'S U B M I T',
style: TextStyle(color: Colors.white),
),
),
),
),