在 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),
   ),
  ),
 ),
),