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

Flutter动态表单数据获取及本地数据库存储问题

Flutter动态表单:获取字段值并本地存储

核心问题分析

原代码存在几个关键问题导致无法有效获取所有表单值:

  • 局部变量(如_mySelection)无法跨组件保留状态
  • Radio组件使用全局变量ids,无法区分不同字段组的选中状态
  • TextField未关联状态存储,输入值无法被捕获
  • 缺少统一的表单数据存储结构,难以批量处理和存储

解决方案实现

以下是重构后的完整代码,包含表单值的统一管理和本地存储功能:

import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart'; // 引入本地存储库

class DynamicForm extends StatefulWidget { // 修正类名拼写(fromdata → DynamicForm)
  const DynamicForm({Key? key}) : super(key: key);

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

class _DynamicFormState extends State<DynamicForm> {
  // 统一存储所有表单字段的值,key为字段id,value为对应输入值
  final Map<String, dynamic> _formValues = {};
  var url = Uri.parse('https://stoplight.io/mocks/khurramsoftware/data/20414976/getdata');

  // 获取API数据
  Future<dynamic> apirequest() async {
    final response = await http.get(url);
    if (response.statusCode == 200) {
      return json.decode(response.body);
    } else {
      throw Exception(response.reasonPhrase);
    }
  }

  // 保存表单数据到本地
  Future<void> _saveFormData() async {
    final prefs = await SharedPreferences.getInstance();
    // 将Map转为JSON字符串存储
    await prefs.setString('form_data', json.encode(_formValues));
    // 提示保存成功
    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(content: Text('表单数据已保存')),
    );
  }

  // 构建动态表单行
  Widget _buildFormRow(Map<String, dynamic> field) {
    final fieldId = field["id"];
    final fieldType = field["type"];
    final fieldName = field["name"];
    final fieldOptions = field["options"] ?? [];

    switch (fieldType) {
      case "select":
        return Container(
          padding: const EdgeInsets.fromLTRB(28, 15, 28, 28),
          child: InputDecorator(
            decoration: InputDecoration(
              labelText: fieldName,
              focusColor: Colors.blue,
              enabledBorder: OutlineInputBorder(
                borderSide: const BorderSide(color: Colors.deepOrangeAccent),
                borderRadius: BorderRadius.circular(10.0),
              ),
              border: OutlineInputBorder(
                borderSide: const BorderSide(color: Colors.deepOrangeAccent),
                borderRadius: BorderRadius.circular(10.0),
              ),
              focusedBorder: OutlineInputBorder(
                borderSide: const BorderSide(color: Colors.deepOrangeAccent),
                borderRadius: BorderRadius.circular(20.0),
              ),
            ),
            child: DropdownButton<dynamic>(
              style: const TextStyle(color: Colors.deepOrangeAccent),
              hint: Text("选择 $fieldName"),
              value: _formValues[fieldId], // 从全局Map获取当前值
              items: fieldOptions.map((option) {
                return DropdownMenuItem(
                  child: Text(option["text"]),
                  value: option["value"],
                );
              }).toList(),
              onChanged: (newVal) {
                setState(() {
                  _formValues[fieldId] = newVal; // 更新全局Map
                });
              },
            ),
          ),
        );

      case "radio":
        return Container(
          padding: const EdgeInsets.fromLTRB(28, 15, 28, 28),
          child: InputDecorator(
            decoration: InputDecoration(
              labelText: fieldName,
              enabledBorder: OutlineInputBorder(
                borderSide: const BorderSide(color: Colors.deepOrangeAccent),
                borderRadius: BorderRadius.circular(10.0),
              ),
              border: OutlineInputBorder(
                borderSide: const BorderSide(color: Colors.deepOrangeAccent),
                borderRadius: BorderRadius.circular(20.0),
              ),
              focusedBorder: OutlineInputBorder(
                borderSide: const BorderSide(color: Colors.deepOrangeAccent),
                borderRadius: BorderRadius.circular(20.0),
              ),
            ),
            child: Column(
              children: fieldOptions.map((option) {
                final index = fieldOptions.indexOf(option);
                return RadioListTile<int>(
                  title: Text(option["text"]),
                  groupValue: _formValues[fieldId], // 从全局Map获取当前选中索引
                  activeColor: Colors.lightBlue,
                  value: index,
                  onChanged: (val) {
                    setState(() {
                      _formValues[fieldId] = val; // 更新全局Map
                    });
                  },
                );
              }).toList(),
            ),
          ),
        );

      default: // text/number/phone类型
        return Padding(
          padding: const EdgeInsets.fromLTRB(28, 15, 28, 28),
          child: TextFormField(
            keyboardType: (fieldType == "number" && fieldId == "amount")
                ? TextInputType.numberWithOptions(decimal: true)
                : fieldType == "text"
                    ? TextInputType.text
                    : TextInputType.phone,
            inputFormatters: [
              (fieldType == "number" && fieldId == "amount")
                  ? FilteringTextInputFormatter.allow(RegExp("[.0-9]"))
                  : fieldType == "text"
                      ? FilteringTextInputFormatter.allow(RegExp("[a-zA-Z á-ú Á-Ú 0-9]"))
                      : FilteringTextInputFormatter.digitsOnly,
            ],
            decoration: InputDecoration(
              labelText: fieldName,
              enabledBorder: OutlineInputBorder(
                borderSide: const BorderSide(color: Colors.deepOrangeAccent),
                borderRadius: BorderRadius.circular(10.0),
              ),
              border: OutlineInputBorder(
                borderSide: const BorderSide(color: Colors.deepOrangeAccent),
                borderRadius: BorderRadius.circular(10.0),
              ),
            ),
            initialValue: _formValues[fieldId], // 设置初始值
            onChanged: (val) {
              _formValues[fieldId] = val; // 实时更新全局Map
            },
          ),
        );
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("动态表单"),
        actions: [
          // 添加保存按钮
          IconButton(
            icon: const Icon(Icons.save),
            onPressed: _saveFormData,
          )
        ],
      ),
      body: SingleChildScrollView(
        physics: const ScrollPhysics(),
        child: FutureBuilder(
          future: apirequest(),
          builder: (context, AsyncSnapshot snapshot) {
            if (snapshot.hasError) {
              return const Center(child: Text('加载失败'));
            }
            if (snapshot.hasData) {
              return Column(
                children: [
                  const SizedBox(height: 40),
                  Container(
                    height: 120,
                    child: CircleAvatar(
                      radius: 80,
                      backgroundImage: NetworkImage(snapshot.data['image_url']),
                    ),
                  ),
                  const SizedBox(height: 20),
                  Text(snapshot.data['service_name']),
                  const SizedBox(height: 5),
                  ListView.builder(
                    physics: const NeverScrollableScrollPhysics(),
                    shrinkWrap: true,
                    itemCount: snapshot.data['fields'].length,
                    itemBuilder: (context, index) {
                      return _buildFormRow(snapshot.data['fields'][index]);
                    },
                  ),
                ],
              );
            }
            return const Padding(
              padding: EdgeInsets.all(20.0),
              child: CircularProgressIndicator(),
            );
          },
        ),
      ),
    );
  }
}

关键改进点

  • 统一状态管理:用_formValues Map存储所有字段值,key为字段id,确保每个字段的状态独立且可追踪
  • Radio组件修复:每个Radio组的选中值单独存储在Map中,避免全局变量冲突
  • TextField值捕获:通过onChanged实时更新Map,无需维护大量TextEditingController
  • 本地存储实现:使用shared_preferences将表单数据转为JSON字符串存储,也可根据需求替换为sqflite等数据库
  • 代码结构优化:将表单行构建逻辑改为接收完整字段对象,提升可读性和维护性

使用说明

  1. 首先在pubspec.yaml中添加依赖:
dependencies:
  flutter:
    sdk: flutter
  shared_preferences: ^2.2.2
  http: ^1.1.0
  1. 运行flutter pub get安装依赖
  2. 使用DynamicForm组件即可实现动态表单的渲染、值捕获和本地存储

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 07:18:30