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

Flutter中点击TextFormField为何触发整个表单重渲染?

问题分析与解决方案

问题根源

  1. Stream重复创建:每次页面build时,groceryItem函数都会生成新的Firestore Stream实例,StreamBuilder检测到Stream变化后会重新发起监听,导致页面反复进入加载状态。
  2. 控制器文本被强制重置:在StreamBuilder的builder回调中,每次有数据返回都会直接给_nameController.text赋值,覆盖用户正在输入的内容,同时触发页面重绘,形成恶性循环。
  3. 键盘弹出触发重绘:点击TextFormField弹出键盘时,MediaQuery.of(context).viewInsets发生变化,触发页面build,进一步加剧上述问题。

修复步骤

  1. 将Stream的创建移到initState中,确保组件生命周期内只创建一次Stream。
  2. 添加初始化标记,仅在首次获取数据时初始化控制器文本,避免覆盖用户输入。
  3. 移除builder中重复的控制器赋值逻辑。

修改后的完整代码

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';

class CrudPage2 extends StatefulWidget {
  final String docId;
  const CrudPage2({Key? key, required this.docId}) : super(key: key);

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

class CrudPage2State extends State<CrudPage2> {
  late String name = "";
  late bool isImportant = false;
  late Stream<DocumentSnapshot<Object?>> _groceryStream;
  bool _isInitialized = false;

  final TextEditingController _nameController = TextEditingController();
  final TextEditingController _importantController = TextEditingController();

  @override
  void initState() {
    super.initState();
    // 仅在初始化时创建一次Stream
    _groceryStream = FirebaseFirestore.instance
        .collection("groceries")
        .doc(widget.docId)
        .snapshots();
    
    // 处理新增数据的初始化
    if (widget.docId == "NEW") {
      _nameController.text = "";
      _importantController.text = "false";
      _isInitialized = true;
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        leading: IconButton(
          icon: const Icon(Icons.arrow_back),
          onPressed: () => Navigator.of(context).pop(),
        ),
        title: const Text("Grocery Item"),
      ),
      body: SizedBox(
        width: double.infinity,
        child: Padding(
          padding: EdgeInsets.only(
              bottom: MediaQuery.of(context).viewInsets.bottom + 20),
          child: StreamBuilder<DocumentSnapshot>(
            stream: _groceryStream, // 使用初始化好的固定Stream
            builder: (BuildContext context, AsyncSnapshot<DocumentSnapshot> streamSnapshot) {
              if (streamSnapshot.connectionState == ConnectionState.waiting) {
                print("****** Loading ******");
                return const Text("Loading");
              } else if (streamSnapshot.hasData) {
                // 仅首次获取数据时初始化控制器和状态变量
                if (!_isInitialized && widget.docId != "NEW") {
                  var jsonData = streamSnapshot.data?.data();
                  Map<String, dynamic> myData = jsonData as Map<String, dynamic>;
                  setState(() {
                    name = myData['name'];
                    isImportant = myData['important'];
                    _nameController.text = name;
                    _importantController.text = isImportant ? "true" : "false";
                    _isInitialized = true;
                  });
                }

                return Column(
                  mainAxisSize: MainAxisSize.min,
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Flexible(
                      child: TextFormField(
                        controller: _nameController,
                        decoration: const InputDecoration(labelText: 'Name'),
                      ),
                    ),
                    StatefulBuilder(
                      builder: (BuildContext context, StateSetter importantStateSetter) {
                        return Row(
                          children: [
                            const Text("Important: "),
                            Switch(
                              value: isImportant,
                              onChanged: (value) {
                                importantStateSetter(() => isImportant = value);
                              },
                            ),
                          ],
                        );
                      },
                    ),
                    Row(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        SizedBox(
                          child: ElevatedButton(
                            child: const Text('Cancel'),
                            onPressed: () async {
                              Navigator.of(context).pop();
                            }),
                        ),
                        const SizedBox(width: 10),
                        SizedBox(
                          child: ElevatedButton(
                            child: const Text("Submit"),
                            onPressed: () async {
                              final String name = _nameController.text;

                              if (widget.docId == 'NEW') {
                                addGroceryItem(name, 1.0, "test", isImportant);
                              } else {
                                updateGroceryItem(widget.docId, name, 1.0, "test", isImportant);
                              }

                              _nameController.text = '';
                              _importantController.text = "";

                              Navigator.of(context).pop();
                            },
                          ),
                        )
                      ],
                    ),
                  ],
                );
              } else {
                return const Text("No Data");
              }
            }
          ),
        ),
      ),
    );
  }

  Future<void> addGroceryItem(
      String name, double quantity, String category, bool isImportant) async {
    await FirebaseFirestore.instance.collection('groceries').add({
      "active": true,
      "name": name,
      "quantity": quantity,
      "category": category,
      "important": isImportant
    });
  }

  Future<void> updateGroceryItem(String docID, String name, double quantity,
      String category, bool isImportant) async {
    await FirebaseFirestore.instance.collection('groceries').doc(docID).update({
      "active": true,
      "name": name,
      "quantity": quantity,
      "category": category,
      "important": isImportant
    });
  }
}

额外说明

  • 移除了原有的groceryItem函数,改为在initState中直接创建Stream,避免每次build生成新实例。
  • 新增_isInitialized标记,确保控制器仅被初始化一次,不会覆盖用户输入。
  • 处理了新增数据(docId为"NEW")的初始化逻辑,避免不必要的Stream监听。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 23:30:26