MrGoose
MrGoose

Reputation: 1

Keep user input in TextFormFields persistent when Navigating back and forth between screens in Flutter

I'm new to flutter and any help would be greatly appreciated.

I have two separate forms on two different screens. Clicking the 'next' button validates the form and runs Navigator.push() to my next screen with the second form, after which if I fill out the TextFormFields of the second form and initialise a save then Navigator.pop() to go back to the first form, the data in the first page is still filled out which is intended however once I run Navigator.push() again to go back to the second form the TextFormFields are empty.

My intention is to have the user data entered in the TextFormFields of both forms persistent after Navigating back and forth through different screens.

I tried using initialValue on the TextFormFields but that didn't work.

To Navigate back I'm using the back button automatically created after I run Navigator.push

Here is the first page:

import '../screens/contact_details_screen.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';

class PersonalDetailsForm extends StatefulWidget {
  @override
  _PersonalDetailsFormState createState() => _PersonalDetailsFormState();
}

class _PersonalDetailsFormState extends State<PersonalDetailsForm> {
  String _title;
  String _firstName;
  String _middleName;
  String _surname;
  String _age;
  String _gender;
  String _dateOfBirth;

  DateTime selectedDate = DateTime.now();
  TextEditingController _date = new TextEditingController();

  final GlobalKey<FormState> _personalDetailsFormKey = GlobalKey<FormState>();

  Widget _buildTitle() {
    return DropdownButtonFormField(
      items: [
        DropdownMenuItem<String>(
          value: 'Dr',
          child: Text('Dr'),
        ),
        DropdownMenuItem<String>(
          value: 'Miss',
          child: Text('Miss'),
        ),
        DropdownMenuItem<String>(
          value: 'Mr',
          child: Text('Mr'),
        ),
        DropdownMenuItem<String>(
          value: 'Mrs',
          child: Text('Mrs'),
        ),
        DropdownMenuItem<String>(
          value: 'Ms',
          child: Text('Ms'),
        ),
        DropdownMenuItem<String>(
          value: 'Prof.',
          child: Text('Prof.'),
        ),
        DropdownMenuItem<String>(
          value: 'Sir',
          child: Text('Sir'),
        ),
      ],
      validator: (String value) {
        if (value == null) {
          return 'Title is required';
        }
        return null;
      },
      value: _title,
      decoration: InputDecoration(labelText: 'Title'),
      onChanged: (String value) {
        setState(() {
          _title = value;
        });
      },
    );
  }

  Widget _buildFirstName() {
    return TextFormField(
      decoration: InputDecoration(labelText: 'First Name'),
      keyboardType: TextInputType.name,
      validator: (String value) {
        if (value.isEmpty) {
          return 'First name is required';
        }
        return null;
      },
      onSaved: (String value) {
        _firstName = value;
      },
    );
  }

  Widget _buildMiddleName() {
    return TextFormField(
      decoration: InputDecoration(labelText: 'Middle Name'),
      keyboardType: TextInputType.name,
      onSaved: (String value) {
        _middleName = value;
      },
    );
  }

  Widget _buildSurname() {
    return TextFormField(
      decoration: InputDecoration(labelText: 'Surname Name'),
      keyboardType: TextInputType.name,
      validator: (String value) {
        if (value.isEmpty) {
          return 'Surname name is required';
        }
        return null;
      },
      onSaved: (String value) {
        _surname = value;
      },
    );
  }

  Widget _buildAge() {
    return TextFormField(
      decoration: InputDecoration(labelText: 'Age'),
      keyboardType: TextInputType.number,
      validator: (String value) {
        int age = int.tryParse(value);

        if (value.isEmpty) {
          return 'Age is required';
        }
        if (age == null || age <= 0) {
          return 'Invalid age';
        }
        return null;
      },
      onSaved: (String value) {
        _age = value;
      },
    );
  }

  Widget _buildGender() {
    return DropdownButtonFormField(
      items: [
        DropdownMenuItem<String>(
          value: 'Male',
          child: Text('Male'),
        ),
        DropdownMenuItem<String>(
          value: 'Female',
          child: Text('Female'),
        ),
        DropdownMenuItem<String>(
          value: 'Other',
          child: Text('Other'),
        )
      ],
      validator: (String value) {
        if (value == null) {
          return 'Gender is required';
        }
        return null;
      },
      value: _gender,
      decoration: InputDecoration(labelText: 'Gender'),
      onChanged: (String value) {
        setState(() {
          _gender = value;
        });
      },
    );
  }

  Widget _buildDateOfBirth() {
    return TextFormField(
      decoration: InputDecoration(labelText: 'Date of Birth'),
      validator: (String value) {
        if (value.isEmpty) {
          return 'Date of birth is required';
        }
        return null;
      },
      onTap: () {
        // Below line stops keyboard from appearing
        FocusScope.of(context).requestFocus(new FocusNode());
        // Show Date Picker Here
        _selectDate(context);
      },
      controller: _date,
    );
  }

  Future<Null> _selectDate(BuildContext context) async {
    DateFormat formatter =
        DateFormat('dd/MM/yyyy'); //specifies day/month/year format

    final DateTime picked = await showDatePicker(
        context: context,
        initialDate: selectedDate,
        firstDate: DateTime(1900),
        lastDate: DateTime.now());
    if (picked != null && picked != selectedDate)
      setState(() {
        selectedDate = picked;
        _date.value = TextEditingValue(text: formatter.format(picked));
        _dateOfBirth = _date.text;
      });
  }

  void nextScreen(BuildContext context) {
    Navigator.of(context).push(MaterialPageRoute(
      builder: (_) {
        return ContactDetailsScreen();
      },
    ));
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      margin: EdgeInsets.all(24),
      child: Form(
        key: _personalDetailsFormKey,
        child: SingleChildScrollView(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              _buildTitle(),
              _buildFirstName(),
              _buildMiddleName(),
              _buildSurname(),
              _buildAge(),
              _buildGender(),
              _buildDateOfBirth(),
              SizedBox(
                height: 50,
              ),
              SizedBox(
                width: double.infinity,
                child: RaisedButton(
                  color: Theme.of(context).primaryColor,
                  onPressed: () {
                    if (!_personalDetailsFormKey.currentState.validate()) {
                      return;
                    }

                    _personalDetailsFormKey.currentState.save();

                    nextScreen(context);
                  },
                  child: Text(
                    'Next',
                    style: TextStyle(color: Colors.white),
                  ),
                ),
              )
            ],
          ),
        ),
      ),
    );
  }
}

Here is the second page:

import 'package:fact_find_v2/screens/testscreen.dart';
import 'package:flutter/material.dart';

class ContactDetailsForm extends StatefulWidget {
  @override
  _ContactDetailsFormState createState() => _ContactDetailsFormState();
}

class _ContactDetailsFormState extends State<ContactDetailsForm> {
  String _email;
  String _phoneNumber;
  String _address;

  final GlobalKey<FormState> _contactDetailsFormKey = GlobalKey<FormState>();

  Widget _buildEmail() {
    return TextFormField(
      decoration: InputDecoration(labelText: 'Email'),
      keyboardType: TextInputType.emailAddress,
      validator: (String value) {
        if (value.isEmpty) {
          return 'Email is required';
        }
        if (!RegExp(
                r"[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?")
            .hasMatch(value)) {
          return 'Please enter a valid email address';
        }
        return null;
      },
      onSaved: (String value) {
        _email = value;
      },
    );
  }

  Widget _buildPhoneNumber() {
    return TextFormField(
      decoration: InputDecoration(labelText: 'Phone Number'),
      keyboardType: TextInputType.phone,
      validator: (String value) {
        if (value.isEmpty) {
          return 'Phone number is required';
        }
        if (!RegExp(r'(^(?:[+0]9)?[0-9]{8,10}$)').hasMatch(value)) {
          return 'Please enter a valid phone number';
        }
        return null;
      },
      onSaved: (String value) {
        _phoneNumber = value;
      },
    );
  }

  Widget _buildAddress() {
    return TextFormField(
        decoration: InputDecoration(labelText: 'Address'),
        keyboardType: TextInputType.streetAddress,
        // initialValue: _addressController.text,
        validator: (String value) {
          if (value.isEmpty) {
            return 'Address is required';
          }
          return null;
        },
        onSaved: (String value) {
          _address = value;
        });
  }

  void nextScreen(BuildContext context) {
    Navigator.of(context).push(
      MaterialPageRoute(
        builder: (_) {
          return TestScreen();
        },
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      margin: EdgeInsets.all(24),
      child: Form(
        key: _contactDetailsFormKey,
        child: SingleChildScrollView(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              _buildEmail(),
              _buildPhoneNumber(),
              _buildAddress(),
              SizedBox(
                height: 50,
              ),
              SizedBox(
                width: double.infinity,
                child: RaisedButton(
                  color: Theme.of(context).primaryColor,
                  onPressed: () {
                    if (!_contactDetailsFormKey.currentState.validate()) {
                      return;
                    }
                    _contactDetailsFormKey.currentState.save();
                    nextScreen(
                        context); //this is the next screen which is just used for debugging
                  },
                  child: Text(
                    'Next',
                    style: TextStyle(color: Colors.white),
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Upvotes: 0

Views: 4062

Answers (1)

bytesizedwizard
bytesizedwizard

Reputation: 6033

If you check the Flutter docs here, you can see that the Navigator.push() method returns an instance of Future<T>. When you call Navigator.pop() from second screen, this will complete the Future for the Navigator.push() method.

So basically you can return the form data on the second screen back to the first screen and then while pushing the second screen again you can pass that data to the second screen.

Here's how you can do it -

  1. Add a new Map variable to _PersonalDetailsFormState screen to save the result for Navigator.push() like so -

    Map<String, Object> contactDetailsResult = Map();
    
  2. You can save the result returned by the Navigator.push() method into a contactDetailsResult and then pass that result as arguments to the ContactDetailsScreen.

    void nextScreen(BuildContext context) async{
      contactDetailsResult = await Navigator.of(context).push(MaterialPageRoute(
        builder: (_) {
          return ContactDetailsScreen(
              email: contactDetailsResult["email"] ?? "",
              phoneNumber: contactDetailsResult["phoneNumber"] ?? "",
              address: contactDetailsResult["address"] ?? "",
          );
        },
      ));
    }
    
    
  3. For popping the route, you will now have to manually add a button either to the app bar or use WillPopScope. More on that can be found here. When popping the route, you need to pass the data from your form like this, which will be saved in the contactDetailsResult variable from the previous step -

    Navigator.of(context).pop({
      "email": email,
      "phoneNumber": phoneNumber,
      "address": address,
    });
    
  4. You can find a similar example in the official flutter docs here.

Also, one thing to note -

This solution does not persist the data in the app. If you pop the first screen then both the forms will be reset. If you wish to persist the data across the app then you should check out the shared_preferences plugin. Tutorial can be found here.

Upvotes: 1

Related Questions