Flutter : 为继承的 StatefulWidget 强制 createState()

问题描述 投票:0回答:1

编辑:通过向小部件

unique key
调用添加
constructor
来解决: 编辑 2:正如 Randal Schwartz 提到的 -> 这不是一个解决方案。问题仍未解决。

  @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,
      );

我有一个

abstract class
(ApiEndpoint) 和两个
implementation
(ApiEndpointName、ApiEndpointUniversal)。

两个

implementation
都有一个
StatefulWidget
(SearchTermInputOneString)
inherit
来自同一个
base class
(SearchTermInput)。

对于两个 ApiEndpoint 对象,SearchTermInputOneString 的 Widget 部分是单独创建的 - 这是正确的。 我的问题是,SearchTermInputOneString 的状态部分仅创建一次 - 因此 ApiEndpointName 和 ApiEndpointUniversal 拥有相同的状态。

我必须在

initState
期间读取特定于 ApiEndpoint 的配置 - 这不起作用,因为仅针对第一个 ApiEndpoint 实例调用
initState
:(

有什么想法可以解决这个问题吗?

abstract class
API端点:

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) {
    ...
  }

ApiEndpoint通用实现:

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) {
    ...
  }
}

abstract
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]);
        }
      },
    );
  }
}

我用这种方式创建 ApiEndpoints 列表:

List<ApiEndpoint> getApiEndPoints({required WidgetRef widgetRef}) {
  final List<ApiEndpoint> apiEndpoints = [
    ApiEndpointName(widgetRef: widgetRef),
    ApiEndpointUniversal(widgetRef: widgetRef),
  ];

  return apiEndpoints;
}
flutter dart inheritance widget stateful
1个回答
0
投票

好吧,我更改了代码,但不幸的是我现在遇到了同样的问题 - 没有为第二个小部件实例创建状态:(

这就是代码现在的样子:

第一个实例的调用:

  @override
  SearchTermInput get searchTermInput => SearchTermInputOneString(
        // key: Key("ApiEndpointName"), // UniqueKey(),
        apiEndpointSearchTermInputContentProvider:
            apiEndpointSearchTermInputContentProvider,
        captionList: ["Name"],
        textInputFormatter: [
          FilteringTextInputFormatter.deny(RegExp(r"[0-9]"))
        ],
        onSubmit: onSubmit,
        onLostFocus: onLostFocus,
      );

第二个实例的调用:

  @override
  SearchTermInput get searchTermInput => SearchTermInputOneString(
        // key: Key("ApiEndpointUniversal"), // UniqueKey(),
        apiEndpointSearchTermInputContentProvider:
            apiEndpointSearchTermInputContentProvider,
        captionList: ["Statement"],
        textInputFormatter: [FilteringTextInputFormatter.allow(RegExp(r".*"))],
        onSubmit: onSubmit,
        onLostFocus: onLostFocus,
      );

StatefulWidget:

class SearchTermInputOneString extends SearchTermInput {
  @override
  final NotifierProvider apiEndpointSearchTermInputContentProvider;

  @override
  final List<String> captionList;

  @override
  final List<TextInputFormatter>? textInputFormatter;

  @override
  final void Function(BuildContext, WidgetRef, List<String>) onSubmit;

  @override
  final void Function(BuildContext, WidgetRef, List<String>) onLostFocus;

  const SearchTermInputOneString({
    super.key,
    required this.apiEndpointSearchTermInputContentProvider,
    required this.captionList,
    required this.textInputFormatter,
    required this.onSubmit,
    required this.onLostFocus,
  });

  @override
  // ignore: no_logic_in_create_state
  ConsumerState<ConsumerStatefulWidget> createState() {
    final log = getLogger();
    log.t("createState");
    return _SearchTermInputOneStringState();
  }
}

class _SearchTermInputOneStringState
    extends ConsumerState<SearchTermInputOneString> {
  late TextEditingController textEditingController;
  late List<String> currentContent;

  @override
  void initState() {
    super.initState();
    final log = getLogger();
    log.t("initstate");
    textEditingController = TextEditingController();
  }

  @override
  void didChangeDependencies() {
    _runsAfterInit(context, ref);
    final log = getLogger();
    log.t("didChangeDependencies");

    // read apiEndpoint content
    currentContent = ref
        .read(widget.apiEndpointSearchTermInputContentProvider)
        .cast<String>();
    textEditingController.text =
        currentContent.isNotEmpty ? currentContent[0] : "";

    super.didChangeDependencies();
  }

  @override
  void dispose() {
    textEditingController.dispose();
    final log = getLogger();
    log.t("dispose");
    super.dispose();
  }

  Future<void> _runsAfterInit(BuildContext context, WidgetRef ref) async {
    await Future.delayed(Duration.zero); // <-- Add a 0 dummy waiting time

    final log = getLogger();
    log.t("_runsAfterInit");

    // set current content
    ref
        .read(currentSearchTermInputContentProvider.notifier)
        .setValue(currentContent);
  }

  @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: widget.textInputFormatter,
        onFieldSubmitted: (_) {
          widget.onSubmit(context, ref, [textEditingController.text]);
        },
      ),
      onFocusChange: (hasFocus) {
        if (!hasFocus) {
          widget.onLostFocus(context, ref, [textEditingController.text]);
        }
      },
    );
  }
}

创建第一个实例后的日志输出:

────────────────────────────────────────────────────────────────────────────────────────
#0   SearchTermInputOneString.createState (package:obituary_viewer/features/obituaries/presentation/widgets/search_term_inputs/search_term_input_one_string.dart:39:9)
┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
2024-11-18 08:26:03.262
┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
 createState
────────────────────────────────────────────────────────────────────────────────────────
────────────────────────────────────────────────────────────────────────────────────────
#0   _SearchTermInputOneStringState.initState (package:obituary_viewer/features/obituaries/presentation/widgets/search_term_inputs/search_term_input_one_string.dart:53:9)
┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
2024-11-18 08:26:03.269
┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
 initstate
────────────────────────────────────────────────────────────────────────────────────────
────────────────────────────────────────────────────────────────────────────────────────
#0   _SearchTermInputOneStringState.didChangeDependencies (package:obituary_viewer/features/obituaries/presentation/widgets/search_term_inputs/search_term_input_one_string.dart:61:9)
┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
2024-11-18 08:26:03.272
┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
 didChangeDependencies
────────────────────────────────────────────────────────────────────────────────────────
────────────────────────────────────────────────────────────────────────────────────────
#0   ApiEndpointNameSearchTermInputContent.build (package:obituary_viewer/features/obituaries/presentation/provider/search_term_input_content_provider.dart:26:9)
┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
2024-11-18 08:26:03.275
┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
 ApiEndpointNameSearchTermInputContent provider build
────────────────────────────────────────────────────────────────────────────────────────
────────────────────────────────────────────────────────────────────────────────────────
#0   DoSearchFunction.build (package:obituary_viewer/features/obituaries/presentation/provider/search_screen_provider.dart:13:9)
┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
2024-11-18 08:26:03.402
┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
 DoSearchFunction provider build
────────────────────────────────────────────────────────────────────────────────────────
────────────────────────────────────────────────────────────────────────────────────────
#0   DoSearchFunction.setValue (package:obituary_viewer/features/obituaries/presentation/provider/search_screen_provider.dart:19:9)
┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
2024-11-18 08:26:03.404
┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
 DoSearchFunction set to Closure: (BuildContext, WidgetRef) => void from Function 'doSearch':.
────────────────────────────────────────────────────────────────────────────────────────
────────────────────────────────────────────────────────────────────────────────────────
#0   _SearchTermInputOneStringState._runsAfterInit (package:obituary_viewer/features/obituaries/presentation/widgets/search_term_inputs/search_term_input_one_string.dart:85:9)
┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
2024-11-18 08:26:03.405
┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
 _runsAfterInit
────────────────────────────────────────────────────────────────────────────────────────

第二个实例后的日志输出:

> flutter:
> ┌─────────────────────────────────────────────────────────────────────────────────────────
> flutter: │ #0   CurrentApiEndpoint.setValue
> (package:obituary_viewer/features/obituaries/presentation/provider/api_endpoint_provider.dart:30:9)
> flutter:
> ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
> flutter: │ 2024-11-18 08:27:41.773 flutter:
> ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
> flutter: │  CurrentApiEndpoints set to Instance of
> 'ApiEndpointUniversal' flutter:
> └─────────────────────────────────────────────────────────────────────────────────────────

就是这样 - 文本编辑控制器的文本仍然是第一个实例的文本:(

© www.soinside.com 2019 - 2024. All rights reserved.