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

Flutter中使用Riverpod联动更新两个关联字段的正确方法

解决Riverpod联动SegmentedButton与TextFormField的状态同步问题

问题场景

在Flutter项目中使用Riverpod实现两个关联表单字段:

  • 分段式课程类型选择按钮(SegmentedButton)
  • 自定义课程类型输入框(TextFormField)

预期交互逻辑:

  • 选择分段按钮中的选项时,自动清空输入框内容
  • 在输入框中输入内容时,自动取消分段按钮的选中状态

当前异常现象:
输入框输入内容时分段按钮的选中状态可正常清除,但选择分段按钮时输入框内容并未被清空。

问题原因

问题核心在于**TextFormField使用了initialValue属性**:
initialValue仅在组件首次初始化时赋值一次,后续即使customLessonType状态被重置为空,TextFormField也不会自动更新显示内容,因为它不会监听状态变化来同步文本。

修复方案

改用TextEditingController管理输入框的文本内容,并在Consumer中监听customLessonType的状态变化,实时同步controller的文本值,确保状态更新时输入框内容能及时刷新。

修复后的完整代码

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

void main() {
  runApp(
    const ProviderScope(child: MyApp()),
  );
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('Segmented Button and TextFormField')),
        body: Padding(
          padding: const EdgeInsets.all(16.0),
          child: Column(
            children: [
              // 分段选择按钮
              SizedBox(
                width: double.infinity,
                child: Consumer(
                  builder: (context, ref, child) {
                    final lessonState = ref.watch(lessonControllerProvider);
                    return SegmentedButton<String>(
                      showSelectedIcon: false,
                      emptySelectionAllowed: true,
                      style: SegmentedButton.styleFrom(
                        shape: const RoundedRectangleBorder(
                          borderRadius: BorderRadius.all(Radius.circular(4)),
                        ),
                      ),
                      segments: [
                        LessonSegment(value: '1', text: '1'),
                        LessonSegment(value: '2', text: '2'),
                        LessonSegment(value: '3', text: '3'),
                      ],
                      selected: {lessonState.lessonType},
                      onSelectionChanged: (Set<String> value) {
                        if (value.isNotEmpty) {
                          final selectedValue = value.first;
                          ref
                              .read(lessonControllerProvider.notifier)
                              .updateField('lessonType', selectedValue);
                        }
                      },
                    );
                  },
                ),
              ),
              const SizedBox(height: 8),
              // 自定义输入框
              Consumer(
                builder: (context, ref, child) {
                  final lessonState = ref.watch(lessonControllerProvider);
                  // 创建TextEditingController并同步状态值
                  final textController = TextEditingController(text: lessonState.customLessonType);
                  return TextFormField(
                    controller: textController,
                    textAlign: TextAlign.center,
                    decoration: InputDecoration(
                      border: const OutlineInputBorder(),
                      hintText: 'custom',
                      hintStyle: Theme.of(context).textTheme.labelLarge,
                      errorMaxLines: 2,
                    ),
                    onChanged: (value) {
                      ref
                          .read(lessonControllerProvider.notifier)
                          .updateField('customLessonType', value);
                    },
                    validator: (value) {
                      if ((lessonState.lessonType.isEmpty &&
                              (value == null || value.isEmpty)) ||
                          (lessonState.lessonType.isNotEmpty &&
                              value?.isNotEmpty == true)) {
                        return 'error';
                      }
                      return null;
                    },
                  );
                },
              ),
            ],
          ),
        ),
      ),
    );
  }
}

class LessonState {
  final String lessonType;
  final String customLessonType;

  LessonState({
    this.lessonType = '',
    this.customLessonType = '',
  });

  LessonState copyWith({
    String? lessonType,
    String? customLessonType,
  }) {
    return LessonState(
      lessonType: lessonType ?? this.lessonType,
      customLessonType: customLessonType ?? this.customLessonType,
    );
  }
}

class LessonController extends StateNotifier<LessonState> {
  LessonController() : super(LessonState());

  void updateField(String field, String value) {
    switch (field) {
      case 'lessonType':
        state = state.copyWith(lessonType: value, customLessonType: '');
        break;
      case 'customLessonType':
        state = state.copyWith(customLessonType: value, lessonType: '');
        break;
    }
  }
}

final lessonControllerProvider =
    StateNotifierProvider<LessonController, LessonState>(
  (ref) => LessonController(),
);

class LessonSegment extends ButtonSegment<String> {
  LessonSegment({required super.value, required String text})
      : super(
          label: Padding(
            padding: const EdgeInsets.symmetric(vertical: 14.0),
            child: Text(text),
          ),
        );
}

补充说明

  • 每次Consumer重建时,都会创建新的TextEditingController并传入最新的customLessonType值,确保输入框内容与状态同步
  • 原有的LessonController逻辑无需修改,因为它已经正确处理了两个字段的互斥状态更新

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 04:27:35