与 GetX 包一起使用 keepalive mixin 是否有任何解决方法?

Is there any workaround for using keepalive mixin alongside the GetX package?

我使用 TabBarView 创建了一个基于滑块的步进器表单,它在切换之前验证输入。它有效,但是当我返回时,状态已重置。当我尝试在选项卡末尾收集数据时,此行为导致我得到一个空表单。

我在谷歌上搜索了几个小时,并尝试将当前 GetView<MyController> 切换为经典 StatefulWidget with AutomaticKeepAliveMixin 但没有成功,所以我将其还原。

我有点卡住了,我想知道是否有任何其他方法可以实现此目的,如果可能的话,GetX 方式。

visual explanation

`

create_account_form_slider.dart

class CreateAccountFormSlider extends GetView<CreateAccountController> {
  @override
  Widget build(BuildContext context) {
    return Expanded(
      child: TabBarView(
        physics: const NeverScrollableScrollPhysics(),
        controller: controller.tabController,
        children: [
          _buildEmailForm(),
          _buildNameForm(),
          _buildPasswordForm(),
        ],
      ),
    );
  }

  Widget _buildEmailForm() {
    return Form(
      key: controller.emailFormKey,
      child: Column(
        children: [
          Spacer(), // Necessary to push the input to the bottom constraint, Align class doesn't work.
          Padding(
            padding: const EdgeInsets.symmetric(horizontal: 20.0),
            child: FormInput(
              focusNode: controller.emailFocusNode,
              margin: EdgeInsets.zero,
              label: 'create_account_form_email'.tr,
              hintText: 'janedoe@example.com',
              textInputAction: TextInputAction.next,
              keyboardType: TextInputType.emailAddress,
              validator: controller.emailValidator,
              onFieldSubmitted: (_) => controller.next(),
            ),
          ),
        ],
      ),
    );
  }

... each form has similar structure (almost identical), so i will not include it here

create_account_controller.dart

class CreateAccountController extends GetxController
    with SingleGetTickerProviderMixin {

  final tabIndex = 0.obs;


  final emailFormKey = GlobalKey<FormState>();
  FormState get emailForm => emailFormKey.currentState;

  final emailFocusNode = FocusNode();
  final email = ''.obs;

  TabController tabController;

  @override
  void onInit() {
    _initTabController();
    super.onInit();
  }

  @override
  void onClose() {
    _disposeFocusNodes();
    _disposeTabController();
    super.onClose();
  }

  /// Initialize tab controller and add a listener.
  void _initTabController() {
    tabController = TabController(vsync: this, length: 3);
    tabController.addListener(_tabListener);
  }

  /// Listen on tab change and update `tabIndex`
  void _tabListener() => tabIndex(tabController.index);

  /// Dispose tab controller and remove its listener.
  void _disposeTabController() {
    tabController.removeListener(_tabListener);
    tabController.dispose();
  }

  /// Dispose all the focus nodes.
  void _disposeFocusNodes() {
    emailFocusNode.dispose();
  }


  /// Animate to the next slide.
  void _nextSlide() => tabController.animateTo(tabIndex() + 1);

  /// Animate to the next slide or submit if current tab is the last tab.
  void next() {
    if (tabIndex().isEqual(0) && emailForm.validate()) {
      _nextSlide();
      return focusScope.requestFocus(nameFocusNode);
    }
    ...
  }

  /// A function that checks the validity of the given value.
  ///
  /// When the email is empty, show required error message and when the email
  /// is invalid, show the invalid message.
  String emailValidator(String val) {
    if (val.isEmpty) return 'create_account_form_error_email_required'.tr;
    if (!val.isEmail) return 'create_account_form_error_email_invalid'.tr;
    return null;
  }

  /// Submit data to the server.
  void _submit() {
    print('TODO: implement submit');
    print(email());
  }
}

我通过保存表单并在我的自定义 FormInput 小部件上添加一个 initialValue 然后将可观察变量放到每个相关的 FormInput 上来实现它。无需使用 keepalive mixin。

create_account_controller.dart

  /// Animate to the next slide or submit if current tab is the last tab.
  void next() {
    if (tabIndex().isEqual(0) && emailForm.validate()) {
      // save the form so the value persisted into the .obs variable
      emailForm.save();
      
      // slide to next form
      _nextSlide();

      // TODO: wouldn't it be nice if we use autofocus since we only have one input each form?
      return focusScope.requestFocus(nameFocusNode);
    }

    ...
  }

create_account_form_slider.dart

Obx( // wrap the input inside an Obx to rebuild with the new value
  () => Padding(
    padding: const EdgeInsets.symmetric(horizontal: 20.0),
    child: FormInput(
      focusNode: controller.emailFocusNode,
      label: 'create_account_form_email'.tr,
      hintText: 'janedoe@example.com',
      textInputAction: TextInputAction.next,
      keyboardType: TextInputType.emailAddress,
      validator: controller.emailValidator,
      onFieldSubmitted: (_) => controller.next(),
      initialValue: controller.email(), // use initial value to keep current value when user go back from the next slide
      onSaved: controller.email, // persist current value into the .obs variable
    ),
  ),
),

仅供参考:FormInput 只是一个常规的 TextInput,只有 decoration 被修改了。这应该适用于常规颤动 TextInput.

如果你想像StatefulWidget一样在GetX中使用AutomaticKeepAliveMixin。您可以像这样在 Get.put 中添加参数 'permanent: true'

Get.put<HomeController>(
  HomeController(),
  permanent: true,
);

HomeBinding 的完整代码如下

import 'package:get/get.dart';

import '../controllers/home_controller.dart';

class HomeBinding extends Bindings {
  @override
  void dependencies() {
    Get.put<HomeController>(
      HomeController(),
      permanent: true,
    );
  }
}