Flutter中使用Riverpod联动更新两个关联字段的正确方法
问题场景
在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
相关产品推荐
相关产品推荐

