Skip to main content

Actions API 修订

摘要

#

在 Flutter 中,Intent 是一个对象,通常使用 Shortcuts 小部件绑定到键盘快捷键组合。Intent 可以绑定到 Action,后者可以更新应用程序的状态或执行其他操作。在使用此 API 的过程中,我们发现设计中存在一些缺点,因此我们更新了 Actions API,使其更易于使用和理解。

在之前的 Actions API 设计中,操作是从 LocalKey 映射到 ActionFactory,后者每次调用 invoke 方法时都会创建一个新的 Action。在当前的 API 中,操作是从 Intent 的类型映射到 Action 实例(使用 Map<Type, Action>),并且不会为每次调用都重新创建它们。

上下文

#

最初的 Actions API 设计面向从窗口小部件调用操作,并让这些操作在窗口小部件的上下文中执行。团队一直在使用操作,并发现该设计中存在一些需要解决的限制:

  1. 操作无法从窗口小部件层次结构外部调用。这方面的例子包括处理命令脚本、一些撤销架构和一些控制器架构。

  2. 从快捷键到 Intent,然后到 Action 的映射并不总是很清晰,因为数据结构将 LogicalKeySet 映射到 Intent,然后将 LocalKey 映射到 ActionFactory。新的映射仍然是 LogicalKeySet 到 Intent,但随后它将 Type(Intent 类型)映射到 Action,这更直接、更易读,因为 Intent 的类型写在映射中。

  3. 如果操作的键绑定位于窗口小部件层次结构的另一部分,则 Intent 并不总是能够访问决定是否应启用 intent/action 的必要状态。

为了解决这些问题,我们对 API 做了一些重大更改。操作的映射变得更直观,启用接口已移至 Action 类。从 Action 的 invoke 方法及其构造函数中删除了一些不必要的参数,并允许操作从其 invoke 方法返回结果。操作被制作成泛型,接受它们处理的 Intent 类型,不再使用 LocalKeys 来标识要运行的操作,而是使用 Intent 的类型。

这些更改中的大部分是在 修改 Action API 和 使 Action.enabled 成为 isEnabled(Intent intent) 而不是 的 PR 中完成的,并在设计文档 中进行了详细说明。

更改说明

#

以下是为解决上述问题所做的更改:

  1. 提供给 Actions 小部件的 Map<LocalKey, ActionFactory> 现在是 Map<Type, Action<Intent>>(类型是要传递给 Action 的 Intent 的类型)。
  2. isEnabled 方法已从 Intent 类移动到 Action 类。
  3. 删除了 Action.invoke 和 Actions.invoke 方法的 FocusNode 参数。
  4. 调用操作不再创建 Action 的新实例。
  5. 从 Intent 构造函数中删除了 LocalKey 参数。
  6. 从 CallbackAction 中删除了 LocalKey 参数。
  7. Action 类现在是泛型 (Action<T extends Intent>),以提高类型安全性。
  8. CallbackAction 使用的 OnInvokeCallback 不再接受 FocusNode 参数。
  9. ActionDispatcher.invokeAction 签名已更改为不接受可选的 FocusNode,而是接受可选的 BuildContext。
  10. 删除了 Action 子类中的 LocalKey 静态常量(按约定命名的键)。
  11. Action.invoke 和 ActionDispatcher.invokeAction 方法现在将调用操作的结果作为 Object 返回。
  12. 现在可以监听 Action 类的状态更改。
  13. ActionFactory typedef 已被删除,因为它不再使用。

示例分析器错误

#

以下是一些示例分析器错误,这些错误可能是由于 Actions API 的过时使用造成的。错误的具体细节可能会有所不同,并且这些更改还可能导致其他错误。

error: MyActionDispatcher.invokeAction' ('bool Function(Action<Intent>, Intent, {FocusNode focusNode})') isn't a valid override of 'ActionDispatcher.invokeAction' ('Object Function(Action<Intent>, Intent, [BuildContext])'). (invalid_override at [main] lib/main.dart:74)

error: MyAction.invoke' ('void Function(FocusNode, Intent)') isn't a valid override of 'Action.invoke' ('Object Function(Intent)'). (invalid_override at [main] lib/main.dart:231)

error: The method 'isEnabled' isn't defined for the type 'Intent'. (undefined_method at [main] lib/main.dart:97)

error: The argument type 'Null Function(FocusNode, Intent)' can't be assigned to the parameter type 'Object Function(Intent)'. (argument_type_not_assignable at [main] lib/main.dart:176)

error: The getter 'key' isn't defined for the type 'NextFocusAction'. (undefined_getter at [main] lib/main.dart:294)

error: The argument type 'Map<LocalKey, dynamic>' can't be assigned to the parameter type 'Map<Type, Action<Intent>>'. (argument_type_not_assignable at [main] lib/main.dart:418)

迁移指南

#

需要进行重大更改才能将现有代码更新到新的 API。

预定义操作的操作映射

#

要更新 Actions 小部件中 Flutter 预定义操作(如 ActivateAction 和 SelectAction)的操作映射,请执行以下操作:

  • 更新 actions 参数的参数类型
  • 在 Shortcuts 映射中使用特定 Intent 类的实例,而不是 Intent(TheAction.key) 实例。

迁移前的代码:

dart
class MyWidget extends StatelessWidget {
  // ...
  @override
  Widget build(BuildContext context) {
    return Shortcuts(
      shortcuts: <LogicalKeySet, Intent> {
        LogicalKeySet(LogicalKeyboardKey.enter): Intent(ActivateAction.key),
      },
      child: Actions(
        actions: <LocalKey, ActionFactory>{
          Activate.key: () => ActivateAction(),
        },
        child: Container(),
      )
    );
  }
}

迁移后的代码:

dart
class MyWidget extends StatelessWidget {
  // ...
  @override
  Widget build(BuildContext context) {
    return Shortcuts(
      shortcuts: <LogicalKeySet, Intent> {
        LogicalKeySet(LogicalKeyboardKey.enter): ActivateIntent,
      },
      child: Actions(
        actions: <Type, Action<Intent>>{
          ActivateIntent: ActivateAction(),
        },
        child: Container(),
      )
    );
  }
}

自定义操作

#

要迁移自定义操作,请消除已定义的 LocalKeys,并将其替换为 Intent 子类,以及更改 Actions 小部件的 actions 参数的参数类型。

迁移前的代码:

dart
class MyAction extends Action {
  MyAction() : super(key);

  /// 将此操作唯一标识给 [Intent] 的 [LocalKey]。
  static const LocalKey key = ValueKey<Type>(RequestFocusAction);

  @override
  void invoke(FocusNode node, MyIntent intent) {
    // ...
  }
}

class MyWidget extends StatelessWidget {
  // ...
  @override
  Widget build(BuildContext context) {
    return Shortcuts(
      shortcuts: <LogicalKeySet, Intent> {
        LogicalKeySet(LogicalKeyboardKey.enter): Intent(MyAction.key),
      },
      child: Actions(
        actions: <LocalKey, ActionFactory>{
          MyAction.key: () => MyAction(),
        },
        child: Container(),
      )
    );
  }
}

迁移后的代码:

dart
// 如果之前使用的是裸 `LocalKey`,则可能需要创建新的 `Intent` 子类。
class MyIntent extends Intent {
  const MyIntent();
}

class MyAction extends Action<MyIntent> {
  @override
  Object invoke(MyIntent intent) {
    // ...
  }
}

class MyWidget extends StatelessWidget {
  // ...
  @override
  Widget build(BuildContext context) {
    return Shortcuts(
      shortcuts: <LogicalKeySet, Intent> {
        LogicalKeySet(LogicalKeyboardKey.enter): MyIntent,
      },
      child: Actions(
        actions: <Type, Action<Intent>>{
          MyIntent: MyAction(),
        },
        child: Container(),
      )
    );
  }
}

带参数的自定义 Actions 和 Intents

#

要更新使用 intent 参数或保存状态的操作,需要修改 invoke 方法的参数。在下面的示例中,代码将参数的值保存在 intent 中,作为操作实例的一部分。这是因为在旧设计中,每次执行操作时都会创建一个新的实例,并且生成的 action 可以由 ActionDispatcher 保留以记录状态。

在下面的迁移后代码示例中,新的 MyAction 将状态作为调用 invoke 的结果返回,因为不会为每次调用创建新的实例。此状态将返回给 Actions.invoke 或 ActionDispatcher.invokeAction 的调用者,具体取决于操作的调用方式。

迁移前的代码:

dart
class MyIntent extends Intent {
  const MyIntent({this.argument});

  final int argument;
}

class MyAction extends Action {
  MyAction() : super(key);

  /// 将此操作唯一标识给 [Intent] 的 [LocalKey]。
  static const LocalKey key = ValueKey<Type>(RequestFocusAction);

  int state;

  @override
  void invoke(FocusNode node, MyIntent intent) {
    // ...
    state = intent.argument;
  }
}

迁移后的代码:

dart
class MyIntent extends Intent {
  const MyIntent({this.argument});

  final int argument;
}

class MyAction extends Action<MyIntent> {
  @override
  int invoke(Intent intent) {
    // ...
    return intent.argument;
  }
}

时间线

#

包含在版本中:1.18
稳定版本中:1.20

参考

#

API 文档:

相关问题:

相关 PR: