在 Flutter 中在屏幕之间来回导航时,保持 TextFormFields 中的用户输入持久化

问题描述

我是 Flutter 的新手,任何帮助将不胜感激。

我在两个不同的屏幕上有两个单独的表单。单击“下一步”按钮验证表单并使用第二个表单将 Navigator.push() 运行到我的下一个屏幕,之后如果我填写第二个表单的 TextFormFields 并初始化保存,则 Navigator.pop() 返回对于第一个表单,仍然填写第一页中的数据,但是一旦我再次运行 Navigator.push() 返回到第二个表单,TextFormFields 为空。

我的目的是让在不同屏幕之间来回导航后在两种表单的 TextFormFields 中输入的用户数据保持不变。

我尝试在 TextFormFields 上使用 initialValue,但没有用。

要返回导航,我使用的是运行 Navigator.push自动创建的返回按钮

这是第一页:

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'),onSaved: (String value) {
        _middleName = value;
      },);
  }

  Widget _buildSurname() {
    return TextFormField(
      decoration: Inputdecoration(labelText: 'Surname 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),)
            ],);
  }
}

这是第二页:

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,children: <Widget>[
              _buildEmail(),_buildPhoneNumber(),_buildAddress(),onpressed: () {
                    if (!_contactDetailsFormKey.currentState.validate()) {
                      return;
                    }
                    _contactDetailsFormKey.currentState.save();
                    nextScreen(
                        context); //this is the next screen which is just used for debugging
                  },);
  }
}

解决方法

如果您查看 Flutter 文档 here,您可以看到 Navigator.push() 方法返回 Future<T> 的实例。 当您从第二个屏幕调用 Navigator.pop() 时,这将完成 Future 方法的 Navigator.push()

所以基本上你可以将第二个屏幕上的表单数据返回到第一个屏幕,然后在再次推动第二个屏幕的同时你可以将该数据传递到第二个屏幕。

您可以这样做 -

  1. 将新的 Map 变量添加到 _PersonalDetailsFormState 屏幕以保存 Navigator.push() 的结果 -

    Map<String,Object> contactDetailsResult = Map();
    
  2. 您可以将 Navigator.push() 方法返回的结果保存到 contactDetailsResult 中,然后将该结果作为参数传递给 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. 要弹出路由,您现在必须手动向应用栏添加按钮或使用 WillPopScope。有关更多信息,请参见 here。弹出路由时,需要像这样从表单中传递数据,这些数据将保存在上一步的 contactDetailsResult 变量中 -

    Navigator.of(context).pop({
      "email": email,"phoneNumber": phoneNumber,"address": address,});
    
  4. 您可以在官方 Flutter 文档 here 中找到类似的示例。

另外,要注意一件事 -

此解决方案不会将数据保留在应用中。如果您弹出第一个屏幕,则两个表单都将被重置。如果您希望在整个应用程序中保留数据,那么您应该查看 shared_preferences 插件。可以在 here 找到教程。