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

如何实现NotificationInput与NotificationController的双向绑定机制?

实现NotificationInput与NotificationController的双向绑定

背景

TextField和TextEditingController天然支持双向绑定:修改输入框内容会同步更新控制器,修改控制器也会同步更新输入框。现在需要基于给定的NotificationController和NotificationInput代码,实现同样的双向绑定效果,同时避免UI与控制器互相通知导致的循环死锁。

现有基础代码

class NotificationValue {
  final String subject;
  final String message;

  const NotificationValue({
    required this.subject,
    required this.message,
  });

  NotificationValue copyWith({
    String? subject,
    String? message,
  }) {
    return NotificationValue(
      subject: subject ?? this.subject,
      message: message ?? this.message,
    );
  }
}

class NotificationController extends ValueNotifier<NotificationValue> {
  NotificationController({
    String subject = '',
    String message = '',
  }) : super(NotificationValue(
          subject: subject,
          message: message,
        ));

  String get subject => value.subject;
  String get message => value.message;

  set subject(String nextValue) {
    value = value.copyWith(
      subject: nextValue,
    );
  }

  set message(String nextValue) {
    value = value.copyWith(
      message: nextValue,
    );
  }
}

class NotificationInput extends StatefulWidget {
  const NotificationInput({
    super.key,
    required this.controller,
  });

  final NotificationController controller;

  @override
  State<NotificationInput> createState() => _NotificationInputState();
}

class _NotificationInputState extends State<NotificationInput> {
  final subjectController = TextEditingController();
  final messageController = TextEditingController();

  @override
  void initState() {
    subjectController.text = widget.controller.subject;
    messageController.text = widget.controller.message;
    super.initState();
  }

  @override
  void dispose() {
    subjectController.dispose();
    messageController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        TextField(controller: subjectController),
        TextField(controller: messageController),
      ],
    );
  }
}

解决方案

要实现双向绑定,需要完成两个方向的同步,并通过值校验避免循环死锁,以下是修改后的完整代码:

class NotificationValue {
  final String subject;
  final String message;

  const NotificationValue({
    required this.subject,
    required this.message,
  });

  NotificationValue copyWith({
    String? subject,
    String? message,
  }) {
    return NotificationValue(
      subject: subject ?? this.subject,
      message: message ?? this.message,
    );
  }
}

class NotificationController extends ValueNotifier<NotificationValue> {
  NotificationController({
    String subject = '',
    String message = '',
  }) : super(NotificationValue(
          subject: subject,
          message: message,
        ));

  String get subject => value.subject;
  String get message => value.message;

  set subject(String nextValue) {
    // 仅当值变化时才更新,避免无意义的通知触发
    if (nextValue != value.subject) {
      value = value.copyWith(subject: nextValue);
    }
  }

  set message(String nextValue) {
    if (nextValue != value.message) {
      value = value.copyWith(message: nextValue);
    }
  }
}

class NotificationInput extends StatefulWidget {
  const NotificationInput({
    super.key,
    required this.controller,
  });

  final NotificationController controller;

  @override
  State<NotificationInput> createState() => _NotificationInputState();
}

class _NotificationInputState extends State<NotificationInput> {
  final subjectController = TextEditingController();
  final messageController = TextEditingController();
  late VoidCallback _controllerListener;

  @override
  void initState() {
    super.initState();
    // 初始化输入框与控制器同步
    _syncControllerToInput();
    // 注册控制器值变化监听
    _controllerListener = () => _syncControllerToInput();
    widget.controller.addListener(_controllerListener);
  }

  @override
  void dispose() {
    // 移除监听,防止内存泄漏
    widget.controller.removeListener(_controllerListener);
    subjectController.dispose();
    messageController.dispose();
    super.dispose();
  }

  // 将控制器的值同步到输入框
  void _syncControllerToInput() {
    // 仅当值不同时才更新输入框,避免循环触发
    if (subjectController.text != widget.controller.subject) {
      subjectController.text = widget.controller.subject;
    }
    if (messageController.text != widget.controller.message) {
      messageController.text = widget.controller.message;
    }
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        TextField(
          controller: subjectController,
          // 输入框变化时同步到控制器
          onChanged: (value) => widget.controller.subject = value,
        ),
        TextField(
          controller: messageController,
          onChanged: (value) => widget.controller.message = value,
        ),
      ],
    );
  }
}

核心逻辑说明

  1. 输入框→控制器同步:通过TextField的onChanged回调,将输入内容同步到NotificationController,控制器内部会先判断值是否变化,只有不同时才更新并触发通知。
  2. 控制器→输入框同步:在initState中注册控制器的监听,当控制器值变化时,调用_syncControllerToInput方法同步到输入框,同样会先做值校验,避免无意义的更新。
  3. 避免循环死锁:双向同步的每一步都添加了值判断,确保只有实际值变化时才触发操作,彻底切断了UI与控制器互相通知的循环链路。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 19:12:50