You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter中Provider实现的多步骤表单无法保留值的问题解决

解决Flutter Provider多步骤表单输入值丢失问题

你的表单切换后丢失输入值,核心问题不是设置Key,而是没有将输入框的内容同步到Provider的UserModal中,也没有在重建页面时从UserModal读取已保存的值。每次切换步骤时,页面Widget会被重新创建,输入框没有绑定持久化的状态,自然会丢失内容。

具体修复方案

1. 完善UserModal的状态更新方法

给UserModal添加更新各个字段的方法,让输入内容能同步到全局状态:

import 'package:flutter/foundation.dart';

class UserModal extends ChangeNotifier {
  String? name;
  String? email;
  String? password;
  String? education;

  int activeIndex = 0;
  int totalIndex = 2;

  // 更新姓名
  void updateName(String value) {
    name = value;
    notifyListeners();
  }

  // 更新邮箱
  void updateEmail(String value) {
    email = value;
    notifyListeners();
  }

  // 更新密码
  void updatePassword(String value) {
    password = value;
    notifyListeners();
  }

  // 更新教育信息
  void updateEducation(String value) {
    education = value;
    notifyListeners();
  }

  changeStep(int index) {
    activeIndex = index;
    notifyListeners();
  }
}

2. 给输入框绑定全局状态

在各个步骤的表单中,通过TextEditingController绑定UserModal的字段,确保输入内容实时同步,页面重建时能恢复之前的输入:

修改BasicDetails组件

class BasicDetails extends StatefulWidget {
  const BasicDetails({Key? key}) : super(key: key);

  @override
  _BasicDetailsState createState() => _BasicDetailsState();
}

class _BasicDetailsState extends State<BasicDetails> {
  GlobalKey<FormState> basicFormKey = GlobalKey<FormState>();
  final TextEditingController _nameController = TextEditingController();
  final TextEditingController _emailController = TextEditingController();
  final TextEditingController _passwordController = TextEditingController();

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    // 从全局状态初始化输入框内容
    final modal = Provider.of<UserModal>(context, listen: false);
    _nameController.text = modal.name ?? '';
    _emailController.text = modal.email ?? '';
    _passwordController.text = modal.password ?? '';
  }

  @override
  void dispose() {
    _nameController.dispose();
    _emailController.dispose();
    _passwordController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Consumer<UserModal>(builder: (context, modal, child) {
      return Form(
        key: basicFormKey,
        child: ListView(
          padding: const EdgeInsets.all(12.0),
          children: [
            Center(
              child: DotStepper(
                activeStep: modal.activeIndex,
                dotRadius: 20.0,
                shape: Shape.pipe,
                spacing: 10.0,
              ),
            ),
            Text(
              "Step ${modal.activeIndex + 1} of ${modal.totalIndex}",
              style: const TextStyle(fontSize: 20.0),
              textAlign: TextAlign.center,
            ),
            TextFormField(
              controller: _nameController,
              decoration: const InputDecoration(labelText: "Name"),
              validator: RequiredValidator(errorText: "Required *"),
              onChanged: modal.updateName,
            ),
            TextFormField(
              controller: _emailController,
              decoration: const InputDecoration(labelText: "Email"),
              validator: MultiValidator([
                RequiredValidator(errorText: "Required *"),
                EmailValidator(errorText: "Not Valid Email"),
              ]),
              onChanged: modal.updateEmail,
            ),
            TextFormField(
              controller: _passwordController,
              decoration: const InputDecoration(labelText: "Password"),
              obscureText: true,
              validator: MinLengthValidator(6, errorText: "Min 6 characters required"),
              onChanged: modal.updatePassword,
            ),
            const SizedBox(height: 12.0),
            SizedBox(
              height: 40.0,
              child: ElevatedButton(
                onPressed: () {
                  if (basicFormKey.currentState?.validate() ?? false) {
                    modal.changeStep(1);
                  }
                },
                child: const Text("Next", style: TextStyle(fontSize: 20.0)),
              ),
            ),
          ],
        ),
      );
    });
  }
}

修改EducationDetails组件

class EducationDetails extends StatefulWidget {
  const EducationDetails({Key? key}) : super(key: key);

  @override
  _EducationDetailsState createState() => _EducationDetailsState();
}

class _EducationDetailsState extends State<EducationDetails> {
  final TextEditingController _educationController = TextEditingController();

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    // 从全局状态初始化输入框内容
    final modal = Provider.of<UserModal>(context, listen: false);
    _educationController.text = modal.education ?? '';
  }

  @override
  void dispose() {
    _educationController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Consumer<UserModal>(builder: (context, modal, child) {
      return ListView(
        padding: const EdgeInsets.all(12.0),
        children: [
          Center(
            child: DotStepper(
              activeStep: modal.activeIndex,
              dotRadius: 20.0,
              shape: Shape.pipe,
              spacing: 10.0,
            ),
          ),
          Text(
            "Step ${modal.activeIndex + 1} of ${modal.totalIndex}",
            style: const TextStyle(fontSize: 20.0),
            textAlign: TextAlign.center,
          ),
          TextFormField(
            controller: _educationController,
            decoration: const InputDecoration(labelText: "Education"),
            validator: RequiredValidator(errorText: "Required *"),
            onChanged: modal.updateEducation,
          ),
          const SizedBox(height: 12.0),
          Row(
            children: [
              Expanded(
                child: SizedBox(
                  height: 40.0,
                  child: ElevatedButton(
                    onPressed: () {
                      modal.changeStep(0);
                    },
                    child: const Text("Back", style: TextStyle(fontSize: 20.0)),
                  ),
                ),
              ),
              const SizedBox(width: 12.0),
              Expanded(
                child: SizedBox(
                  height: 40.0,
                  child: ElevatedButton(
                    onPressed: () {
                      // 可以在此处获取所有保存的表单值
                      print("Name: ${modal.name}");
                      print("Email: ${modal.email}");
                      print("Password: ${modal.password}");
                      print("Education: ${modal.education}");
                    },
                    child: const Text("Register", style: TextStyle(fontSize: 20.0)),
                  ),
                ),
              ),
            ],
          ),
        ],
      );
    });
  }
}

关键说明

  • 通过TextEditingController绑定输入框,配合didChangeDependencies初始化内容,确保页面重建时能恢复之前的输入。
  • onChanged回调实时将输入内容同步到UserModal,让全局状态和输入框内容保持一致。
  • 不需要额外设置Key,核心是让输入值和全局状态绑定,而非依赖Widget的缓存。

内容的提问来源于stack exchange,提问作者best_of_man

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 00:25:15