Flutter:如何为继承的StatefulWidget强制生成独立State
我定义了抽象类ApiEndpoint,有两个实现类ApiEndpointName和ApiEndpointUniversal。这两个类都持有一个继承自抽象StatefulWidgetSearchTermInput的SearchTermInputOneString组件。
目前的问题是:两个ApiEndpoint实例的SearchTermInputOneString组件本身是分别创建的,但它们的State实例却只创建了一个,导致两个组件共享同一个State。我需要在initState中读取每个ApiEndpoint专属的配置,但initState只在第一个实例创建时调用,无法满足需求。
已尝试的无效方案
曾尝试在SearchTermInputOneString构造函数中添加UniqueKey,但问题仍未解决:
@override SearchTermInput get searchTermInput => SearchTermInputOneString( key: UniqueKey(), currentSearchTermInputContent: widgetRef.read(apiEndpointUniversalSearchTermInputContentProvider), captionList: ["Statement"], // todo : allow umlauts! or better deny numbers? inputFormatterAllow: RegExp(".*"), onSubmit: onSubmit, onLostFocus: onLostFocus, );
枚举与抽象类
enum ApiEndpointType { today, unseen, universal, name, exactAge, ageBetween, ageGreaterThanOrEqual, ageLessThanOrEqual } abstract class ApiEndpoint { ApiEndpointType get type; String get title; SearchTermInput get searchTermInput; String get searchFunctionName; bool validateData(BuildContext context, WidgetRef ref); void onSubmit( BuildContext context, WidgetRef ref, List<String> searchTermInputContent); void onLostFocus( BuildContext context, WidgetRef ref, List<String> searchTermInputContent); }
ApiEndpointName实现
class ApiEndpointName implements ApiEndpoint { final WidgetRef widgetRef; ApiEndpointName({required this.widgetRef}); @override String get searchFunctionName => "getObituariesByName"; @override SearchTermInput get searchTermInput => SearchTermInputOneString( currentSearchTermInputContent: widgetRef.read(apiEndpointNameSearchTermInputContentProvider), captionList: ["Name"], inputFormatterAllow: RegExp("[a-zA-Z% ]"), onSubmit: onSubmit, onLostFocus: onLostFocus, ); @override String get title => "... by name"; @override ApiEndpointType get type => ApiEndpointType.name; @override bool validateData(BuildContext context, WidgetRef ref) { ... } @override void onSubmit(BuildContext context, WidgetRef ref, List<String> searchTermInputContent) { ... } @override void onLostFocus(BuildContext context, WidgetRef ref, List<String> searchTermInputContent) { ... } }
ApiEndpointUniversal实现
class ApiEndpointUniversal implements ApiEndpoint { final WidgetRef widgetRef; ApiEndpointUniversal({required this.widgetRef}); @override String get searchFunctionName => "getObituariesUniversal"; @override SearchTermInput get searchTermInput => SearchTermInputOneString( currentSearchTermInputContent: widgetRef.read(apiEndpointUniversalSearchTermInputContentProvider), captionList: ["Statement"], inputFormatterAllow: RegExp(".*"), onSubmit: onSubmit, onLostFocus: onLostFocus, ); @override String get title => "... universal"; @override ApiEndpointType get type => ApiEndpointType.universal; @override bool validateData(BuildContext context, WidgetRef ref) { ... } @override void onSubmit(BuildContext context, WidgetRef ref, List<String> searchTermInputContent) { ... } @override void onLostFocus(BuildContext context, WidgetRef ref, List<String> searchTermInputContent) { ... } }
抽象StatefulWidget及实现
abstract class SearchTermInput extends ConsumerStatefulWidget { const SearchTermInput({super.key}); List<String> get currentSearchTermInputContent; List<String>? get captionList; RegExp get inputFormatterAllow; void Function(BuildContext, WidgetRef, List<String>) get onSubmit; void Function(BuildContext, WidgetRef, List<String>) get onLostFocus; } class SearchTermInputOneString extends SearchTermInput { @override final List<String> currentSearchTermInputContent; @override final List<String> captionList; @override final RegExp inputFormatterAllow; @override final void Function(BuildContext, WidgetRef, List<String>) onSubmit; @override final void Function(BuildContext, WidgetRef, List<String>) onLostFocus; const SearchTermInputOneString({ super.key, required this.currentSearchTermInputContent, required this.captionList, required this.inputFormatterAllow, required this.onSubmit, required this.onLostFocus, }); @override ConsumerState<ConsumerStatefulWidget> createState() => _SearchTermInputOneStringState(); } class _SearchTermInputOneStringState extends ConsumerState<SearchTermInputOneString> { late TextEditingController textEditingController; @override void initState() { super.initState(); textEditingController = TextEditingController(); textEditingController.text = widget.currentSearchTermInputContent.isNotEmpty ? widget.currentSearchTermInputContent[0] : ""; } @override void dispose() { textEditingController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Focus( child: TextFormField( controller: textEditingController, decoration: InputDecoration( border: const OutlineInputBorder(), labelText: widget.captionList.isNotEmpty ? widget.captionList[0] : "", suffixIcon: IconButton( onPressed: () { textEditingController.text = ""; }, icon: Icon(Icons.clear), ), ), inputFormatters: [ FilteringTextInputFormatter.allow(widget.inputFormatterAllow) ], onFieldSubmitted: (_) { widget.onSubmit(context, ref, [textEditingController.text]); }, ), onFocusChange: (hasFocus) { if (!hasFocus) { widget.onLostFocus(context, ref, [textEditingController.text]); } }, ); } }
ApiEndpoint列表创建方式
List<ApiEndpoint> getApiEndPoints({required WidgetRef widgetRef}) { final List<ApiEndpoint> apiEndpoints = [ ApiEndpointName(widgetRef: widgetRef), ApiEndpointUniversal(widgetRef: widgetRef), ]; return apiEndpoints; }
1. 使用稳定的唯一Key区分Widget实例
UniqueKey()会在每次重建时生成新Key,反而可能引发不必要的重建。应该使用基于ApiEndpoint类型的ValueKey,确保每个ApiEndpoint对应的Widget有固定的唯一标识,让Flutter能区分不同实例,避免State复用:
// ApiEndpointName中 @override SearchTermInput get searchTermInput => SearchTermInputOneString( key: ValueKey(ApiEndpointType.name), // 其他参数保持不变 ); // ApiEndpointUniversal中 @override SearchTermInput get searchTermInput => SearchTermInputOneString( key: ValueKey(ApiEndpointType.universal), // 其他参数保持不变 );
2. 重写didUpdateWidget同步配置更新
即使Key正确,当Widget的配置参数变化时,State不会自动重新初始化。需要在_SearchTermInputOneStringState中重写didUpdateWidget方法,及时更新控制器内容:
@override void didUpdateWidget(covariant SearchTermInputOneString oldWidget) { super.didUpdateWidget(oldWidget); // 当配置内容变化时更新输入框文本 if (widget.currentSearchTermInputContent != oldWidget.currentSearchTermInputContent) { textEditingController.text = widget.currentSearchTermInputContent.isNotEmpty ? widget.currentSearchTermInputContent[0] : ""; } }
3. 检查Widget树的渲染逻辑
确保多个SearchTermInputOneString是作为独立子Widget存在,而非通过条件渲染在同一位置切换。如果使用IndexedStack等切换组件,必须给每个子Widget设置唯一Key,避免Flutter复用State。
4. 优化Widget实例创建逻辑(可选)
当前searchTermInput是getter,每次访问都会创建新的SearchTermInputOneString实例,可能导致不必要的重建。可以改为成员变量,在构造函数中初始化:
class ApiEndpointName implements ApiEndpoint { final WidgetRef widgetRef; final SearchTermInput _searchTermInput; ApiEndpointName({required this.widgetRef}) : _searchTermInput = SearchTermInputOneString( key: ValueKey(ApiEndpointType.name), currentSearchTermInputContent: widgetRef.read(apiEndpointNameSearchTermInputContentProvider), captionList: ["Name"], inputFormatterAllow: RegExp("[a-zA-Z% ]"), onSubmit: onSubmit, onLostFocus: onLostFocus, ); @override SearchTermInput get searchTermInput => _searchTermInput; // 其他代码不变 }
内容的提问来源于stack exchange,提问作者user28298940

