'Flutter: onSaved method of TextFormField not storing values to variables

I have a form which prompts the user to enter the address. The form is broken down into multiple TextFormField widgets which is defined in a file called address_widget.dart. These widgets are called from the file: AddressPage.dart. The form loads and gets validated properly but the values saved by the onSaved methods in address_widget.dart is not getting stored in the variables city and address in AddressPage.dart. When printing the variables city and address, null is printed. Is this the correct way to do it or am I missing something?

address_widget.dart

Widget buildCity(city) {
    return TextFormField(
      style: TextStyle(
        fontFamily: 'Montserrat',
        fontSize: 2 * SizeConfig.textMultiplier,
      ),
      decoration: InputDecoration(
        labelText: 'City',
      ),
      validator: (String value) {
        if (value.isEmpty) {
          return 'This field is Required';
        }
      },
      onSaved: (String value) {
        city = value;
      },
    );
}

Widget buildAddress(address) {
    return TextFormField(
      style: TextStyle(
        fontFamily: 'Montserrat',
        fontSize: 2 * SizeConfig.textMultiplier,
      ),
      decoration: InputDecoration(
        labelText: 'Address',
      ),
      validator: (String value) {
        if (value.isEmpty) {
          return 'This field is Required';
        }
      },
      onSaved: (String value) {
        address = value;
      },
    );
}

AddressPage.dart

import 'package:userproject/widgets/address_widget.dart';

class AddressPage extends StatefulWidget {
  @override
  _AddressPageState createState() => _AddressPageState();
}

class _AddressPageState extends State<AddressPage> {
  
  final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
  final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
  String city;
  String address;  
  

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      key: _scaffoldKey,
      body: Form(
               key: _formKey,
               child: Column(
                      children: <Widget>[
                        Text(
                          'Enter your Address',
                          style: TextStyle(
                            fontFamily: 'Montserrat',
                            fontWeight: FontWeight.bold,
                          ),
                        buildCity(city),
                        buildAddress(address),
                        Row(
                          children: [
                            RaisedButton(
                              onPressed: () {
                                if (!_formKey.currentState.validate()) {
                                       return;
                                 }
                                 _formKey.currentState.save(); 

                                print("city - $city"); // prints null
                                print("address- $address"); // prints null
                              },
                            ),
                          ],
                        )
                      ],
                    ),
                  ),
                ),
    }
}

The code has been simplified for easy reference.



Solution 1:[1]

You would have to use a TextEditingController to get the data from the text field.




class _AddressWidgetState extends State<AddressWidgetScreen> {
  
  final myController = TextEditingController();
//a string to store the input from the text field
  String city = '';

  @override
  void dispose() {
    // Clean up the controller when the widget is disposed.
    myController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
//in your case TextFormField
        TextField(
  controller: myController,
),
 RaisedButton(
            onPressed: () {
              //store the data from textfield to our new string
              city = myController.text.toString();
      //therest of your logic
            },
   
            textColor: Colors.white,),
                   

BONUS TIP

Also, as good practice you should look to validate forms in a different class on runtime, using ValueObjects

Solution 2:[2]

Instead of using the onSaved you could try using the onChange callBack method it get assigned when ever the value gets changed. In order to use the on SavedMethod the a separate formKey must be added to the form section and then we should call the onSaved using the context of the key which we have assigned.

Solution 3:[3]

Have you considered Hot Restart? some code changes are not implemented until you do a 'Hot Restart'

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Ferdinand
Solution 2 Abdul Kadhar
Solution 3 Ahmed Nabil