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

如何同步BlocState流变量与TextFormField?Firestore集成问题

解决方案:Firestore Streams + Flutter Bloc 架构优化及问题修复

核心问题分析

你的问题根源在于BlocBuilder 每次重建时覆盖了本地状态:

  1. 每次 Bloc 状态变更触发 builder 重建时,你都会重新给 selectedAvatar 和 fullNameController.text 赋值,直接覆盖用户通过 setState 修改的内容,导致本地操作无效。
  2. 输入框报错是因为控制器文本在 build 方法中被频繁重置,破坏了 InputConnection 的活跃状态。

下面是针对性的优化方案:


一、重构 UserCubit 逻辑

优化流订阅管理、状态区分及错误处理,避免不必要的状态覆盖:

class UserCubit extends Cubit<UserState> {
  final _firestore = FirebaseFirestore.instance;
  final User? _currentUser = FirebaseAuth.instance.currentUser;
  StreamSubscription? _userSubscription;

  UserCubit() : super(UserMainLoadingState()) {
    _listenToUserDocument();
  }

  // 统一管理Firestore流订阅
  void _listenToUserDocument() {
    if (_currentUser?.uid == null) {
      emit(UserNotExists());
      return;
    }

    _userSubscription = _firestore
        .collection("sample")
        .doc(_currentUser!.uid)
        .snapshots()
        .listen((event) {
      event.exists
          ? emit(UserExists(sample: SampleModel.fromjson(event.data()!, event.id)))
          : emit(UserNotExists());
    }, onError: (error) {
      emit(UserErrorState(error: error.toString()));
    });
  }

  Future<void> addSampleUser({required SampleModel sample}) async {
    if (_currentUser?.uid == null) return;
    emit(UserSideLoadingState());
    try {
      await _firestore
          .collection('sample')
          .doc(_currentUser!.uid)
          .set(sample.toJson());
      emit(UserSavedUpdatedState());
    } catch (e) {
      emit(UserErrorState(error: e.toString()));
      // 提交失败后恢复之前的状态,避免UI一直停留在加载
      final currentState = state;
      if (currentState is UserExists) emit(currentState);
      else if (currentState is UserNotExists) emit(currentState);
    }
  }

  // 取消订阅避免内存泄漏
  @override
  Future<void> close() {
    _userSubscription?.cancel();
    return super.close();
  }
}

新增错误状态类:

class UserErrorState extends UserState {
  final String error;
  const UserErrorState({required this.error});

  @override
  List<Object?> get props => [error, sampleModel];
}

二、Widget 层分离本地与 Bloc 状态

表单临时编辑状态(输入框内容、选中头像)由 Widget 自身维护,仅在 Bloc 初始加载或远程数据更新时同步初始数据:

class _MyWidgetState extends State<MyWidget> {
  final fullNameKey = GlobalKey<FormState>();
  final fullNameController = TextEditingController();
  bool _formChecked = false;
  Avatar? selectedAvatar;

  @override
  void initState() {
    super.initState();
    // 初始化时同步Bloc中的已有用户数据
    final cubit = BlocProvider.of<UserCubit>(context, listen: false);
    if (cubit.state is UserExists) {
      _syncUserToLocal((cubit.state as UserExists).sampleModel!);
    }
  }

  // 统一同步远程数据到本地状态
  void _syncUserToLocal(SampleModel sample) {
    fullNameController.text = sample.name;
    setState(() {
      selectedAvatar = sample.avatar == "boy" ? Avatar.boy : Avatar.girl;
    });
  }

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: BlocConsumer<UserCubit, UserState>(
        listener: (context, state) {
          if (state is UserSavedUpdatedState) {
            print("Saved/Updated User");
            context.goNamed(Routes.profileMain);
          }
          // 远程数据更新时同步到本地(比如其他页面修改了用户信息)
          if (state is UserExists) {
            _syncUserToLocal(state.sampleModel!);
          }
        },
        builder: (context, state) {
          if (state is UserMainLoadingState) {
            return const Center(child: CircularProgressIndicator());
          }
          return Scaffold(
            body: Column(
              children: [
                Row(
                  children: [
                    IconButton(
                      onPressed: () => setState(() => selectedAvatar = Avatar.girl),
                      icon: const Icon(Icons.girl),
                    ),
                    IconButton(
                      onPressed: () => setState(() => selectedAvatar = Avatar.boy),
                      icon: const Icon(Icons.boy),
                    )
                  ],
                ),
                Form(
                  key: fullNameKey,
                  child: TextFormField(
                    autovalidateMode: _formChecked
                        ? AutovalidateMode.always
                        : AutovalidateMode.disabled,
                    validator: (value) {
                      if (value == null || value.trim().isEmpty) {
                        return "Name cannot be empty";
                      }
                      if (value.length < 3) {
                        return "Username must be greater than 3 characters";
                      }
                      return null;
                    },
                    controller: fullNameController,
                    decoration: const InputDecoration(labelText: "Full Name"),
                  ),
                ).marginDown(),
                FilledButton(
                  onPressed: () {
                    setState(() => _formChecked = true);
                    if (fullNameKey.currentState!.validate() && selectedAvatar != null) {
                      final sample = SampleModel(
                        name: fullNameController.text.trim(),
                        avatar: selectedAvatar == Avatar.boy ? "boy" : "girl",
                      );
                      BlocProvider.of<UserCubit>(context).addSampleUser(sample: sample);
                    }
                  },
                  child: const Text("Submit"),
                )
              ],
            ),
          );
        },
      ),
    );
  }

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

三、关键优化点说明

  1. 状态分离:本地表单临时状态由 Widget 维护,Bloc 仅负责远程数据的拉取、提交和全局状态同步,避免 build 时覆盖用户操作。
  2. 流订阅管理:在 Cubit 中统一管理 Firestore 流的订阅与取消,防止内存泄漏。
  3. 数据同步时机:仅在初始化、远程数据更新时同步到本地,而非每次 build 都执行,彻底解决输入框重置问题。
  4. 错误处理:新增错误状态,提交失败后恢复之前的状态,提升用户体验。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 05:15:30