How to validate textfield when posting to firestore in flutter?

Viewed 208

I have added validator in TextField but validator is not working while submitting data. Save button saves the data with null value.

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),
                      ),
                    ),
                  ),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
 }
3 Answers

It appears that the problem is a simple mistake with your control flow in the following code (see // comment):

 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);
    }
  }

As you can see, even if the user input is not valid, you still continue to upload the results to Firebase anyway.

Try correcting like this:

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
    }
  }

The only possible reason is because of _addressType it's initial value is null since it's a String? variable so if the user didn't select a "Delivery Slot" the result of it after saving would be null,

what you need to do, it to check the value of _addressType if it's null or not then proceed to saving the form because your form doesn't check it if it's null or not.

You can add the form validator to the submit button itself. The below code works when you click on the submit button.

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