跳至主要内容

Actions API 修订版

摘要

#

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

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

上下文

#

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

  1. 无法从组件层次结构外部调用操作。此类示例包括处理命令脚本、某些撤消架构和某些控制器架构。

  2. 从快捷键到 Intent,然后到 Action 的映射并不总是很清楚,因为数据结构映射了 LogicalKeySet =>Intent,然后 LocalKey => ActionFactory。新的映射仍然是 LogicalKeySetIntent,但随后它将 TypeIntent 类型)映射到 Action,这更直接且更易于阅读,因为意图的类型是在映射中编写的。

  3. 如果操作的键绑定位于组件层次结构的另一部分,则 Intent 并不总是能够访问决定是否应启用意图/操作所需的状态。

为了解决这些问题,我们对 API 进行了一些重大更改。操作的映射变得更加直观,并且已将启用接口移动到 Action 类。从 Actioninvoke 方法及其构造函数中删除了一些不必要的参数,并且允许操作从其 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.invokeActions.invoke 方法的 FocusNode 参数。
  4. 调用操作不再创建 Action 的新实例。
  5. 已删除 Intent 构造函数的 LocalKey 参数。
  6. 已删除 CallbackActionLocalKey 参数。
  7. Action 类现在是泛型 (Action<T extends Intent>),以提高类型安全性。
  8. CallbackAction 使用的 OnInvokeCallback 不再接受 FocusNode 参数。
  9. ActionDispatcher.invokeAction 签名已更改,不再接受可选的 FocusNode,而是接受可选的 BuildContext
  10. 已删除 Action 子类中的 LocalKey 静态常量(按照惯例命名为键)。
  11. Action.invokeActionDispatcher.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。

预定义操作的操作映射

#

要更新 Flutter 中预定义操作(如 ActivateActionSelectAction)的 Actions 组件中的操作映射,请执行以下操作

  • 更新 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);

  /// The [LocalKey] that uniquely identifies this action to an [Intent].
  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
// You may need to create new Intent subclasses if you used
// a bare LocalKey before.
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(),
      )
    );
  }
}

带有参数的自定义 ActionsIntents

#

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

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

迁移前代码

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

  final int argument;
}

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

  /// The [LocalKey] that uniquely identifies this action to an [Intent].
  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